Custom asset sources

How to add custom asset sources for Sanity Studio.

Sanity Studio comes with a rudimentary asset selector out of the box. It lets you browse and select images you have already uploaded. You can also add multiple asset sources, or replace the default one, either globally or for a specific asset field.

Asset source plugins

You can find available asset source plugins in this overview, or by searching for them on npmjs.com. If the package name starts with sanity-plugin-asset-plugin-name, you can install them using the Sanity CLI tool in your studio project folder: sanity install asset-plugin-name. This will add it to the dependencies in package.json, as well as to the plugins array in sanity.json. Some asset plugins will require that you add some configuration, for example an API token. In most cases you can find this configuration file in the config/ folder in your studio project folder.

Gotcha

You may have to restart the development server if you have changed a config file.

When adding image asset source plugins to your studio, the Select button for the image field will become a drop down button, showing the multiple sources:

In this example, a plugin called sanity-plugin-asset-source-cloudinary was added to the studio, to let the user select images from a Cloudinary widget in their image types.

Defining asset sources globally

You can override every image field to only support a specified set of sources by leveraging the parts system. More specifically, by implementing the part part:@sanity/form-builder/input/image/asset-sources and referecing the implementation in your Studio's sanity.json file located on the root level of your folder.

Let's say you want to enable the Cloudinary asset source only. First install the plugin by running sanity install asset-source-cloudinary. Remember to add your API keys in the cloudinary config file located in the Studio’s config folder. Then, in sanity.json, add this code snippet to the parts array:

{
  "implements": "part:@sanity/form-builder/input/image/asset-sources",
  "path": "./parts/assetSources.js"
}

This code snippet tells the studio to go to a file called assetSources.js that's located in a folder called parts inside your studio’s project folder. You should create this file (and folder if you don't have done so already). This file should export an array of asset sources. So if you only want to enable the Cloudinary source, it would have to look like this:

import Cloudinary from 'part:sanity-plugin-asset-source-cloudinary'

export default [Cloudinary]

The partname for a specific plugin can be found in the sanity.json file on the root of that plugin's source (and should preferably be menitoned in its documentation).

The built in default source has the part name part:@sanity/form-builder/input/image/asset-source-default. Import the part, and add that to the array if you want to keep it.

Using sources on a single type

You can customize sources for single image type field in the schema via the options.sources property:

import Default from 'part:@sanity/form-builder/input/image/asset-source-default'
import Cloudinary from 'part:sanity-plugin-asset-source-cloudinary'

{
  name: 'bannerImage',
  title: 'Banner image',
  type: 'image',
  options: {sources: [Default, Cloudinary]}
}

Implementing image asset source plugins

The plugin exports an object like this implementing part:@sanity/form-builder/input/image/asset-source

export default {
  name: 'cloudinary', // Unique source name
  title: 'Cloudinary', // Title displayed in lists, buttons etc
  component: Cloudinary, // Selection component
  icon: Icon // Icon for lists, buttons etc.
}

It is then listed in the plugin's sanity.json file:

{
  "paths": {
    "source": "./src",
    "compiled": "./dist/lib"
  },
  "parts": [
    {
      "name": "part:sanity-plugin-asset-source-unsplash/image-asset-source",
      "implements": "part:@sanity/form-builder/input/image/asset-source",
      "path": "index.js"
    }
  ]
}

The name is something you pick yourself, but follow the naming conventions described here.

The selection component

The part exports a component that will let the user select some asset(s) from somewhere.

If the user selects something, the component calls the props.onSelect function with an array of asset objects like this:

type Asset {
  kind: 'url' | 'base64' | 'file' | 'assetDocumentId'
  value: string | File
  assetDocumentProps?: {
    originalFilename?: string,
    source?: {
      name: string,
      id: string,
      url?: string
    },
    label?: string,
    title?: string,
    description?: string,
    creditLine?: string
  }
}

An asset can be a URL, user agent File object, base64 encoded binary data or an assetDocumentId. It can have assetDocumentProps that will end up as properties on the resulting asset document. The allowed document props are:

  • originalFilenameString

    If you would like to use the original filename, when saving the file etc.

  • sourceObject

    {name, id, url?} - Optional object identifying the asset in the source, so you can find all assets from that source, or find it back to the spesific assets when opening the plugin etc. If set, the object properties name and id are required, but url is optional. An example for Instragram images: {name: 'instragram', id: '_cjqbJKwZB', url: 'https://www.instagram.com/p/_cjqbJKwZB/'}

  • titleString

    Optional title for the asset.

  • descriptionString

    Optional description for the asset.

  • creditLineString

    Optional credit line for the asset. E.g. John Doe by Instragram

  • labelString

    Optional label.

Component Props

onSelect(Asset[])

When assets are selected and returned to props.onSelect, the Studio will make sure to upload the asset(s). If the selected asset is uploaded previously, the existing asset document and file will be used instead.

onClose()

The component must call props.onClose if the select action is canceled or closed somehow.

selectedAssets

An array of Sanity assets if they are selected in the opening interface. These are Sanity asset documents.

selectionType

If the opening interface selection type is 'single' or 'multiple'

document

The document where the asset in going to be inserted into.

Basic component example

Using the part:@sanity/components/dialogs/fullscreen we can make a closeable interface, and do a simple component where you can select a single image:

import React from 'react'
import Dialog from 'part:@sanity/components/dialogs/fullscreen'

export default class GitHubAssetSource extends React.Component {
  static defaultProps = {
    selectedAssets: undefined
  }

  handleSelect = () => {
    this.props.onSelect([{
        kind: 'url',
        value: 'https://github.githubassets.com/images/modules/site/sponsors/logo-mona.svg',
        assetDocumentProps: {
          originalFilename: 'logo-mona.svg', // Use this filename when the asset is saved as a file by someone.
          source: {
            // The source this image is from 
           name: 'github.githubassets.com',
           // A string that uniquely idenitfies it within the source.
           // In this example the URL is the closest thing we have as an actual ID.
           id: 'https://github.githubassets.com/images/modules/site/sponsors/logo-mona.svg'
          },
          description: 'Mona Lisa Octocat',
          creditLine: 'By Github.com'
        }
      }]
    )
  }

  handleClose = () => {
    this.props.onClose()
  }

  render() {
    return (
      <Dialog title="Select image from Github" onClose={this.handleClose} isOpen>
        <div>
          <img src="https://github.githubassets.com/images/modules/site/sponsors/logo-mona.svg" onClick={this.handleSelect} />
        </div>
      </Dialog>
    )
  }
}

Gotcha

CORS HEADERS FOR IMAGE URLS
When calling onSelect with kind: 'url' the resource must respond with a access-control-allow-origin header, allowing the image to be read by the Studio host. For instance * - which will allow all hosts.

Protip

BEST PRACTICES
When integrating with an external service, be sure to read the usage guidelines for that service or API. Some will require you to honour the credits for the asset, not expose any API keys etc. Use the assetDocumentProps for onSelect to store any required or relevant information to the resulting asset document. If it is from a service where the asset has an ID and can be displayed in the service, you should use the source key for the assetDocumentProps to store that information. In that way you can find back to the original asset.