Studio

Plugins API

Extend the capabilities of your studio using plugins

The plugin configuration property accepts an array of plugin definitions. The plugin configuration accepts most of the same properties as the workspace config API, the notable exceptions being dataset, projectId, auth, and theme.

Protip

import { definePlugin } from 'sanity'

export const previewUrlPlugin = definePlugin({
  name: 'preview-url-plugin',
  document: {
    productionUrl: async (prev, { document }) => {
      const slug = document.slug?.current
      return slug ? `https://some-custom-url.xyz/${slug}` : prev
    },
  },
})

Properties

  • Requiredname

    string

    Unique identifier for the plugin.

  • document

    object | DocumentPluginOptions

    Accepts custom components for document actions and badges, as well as a custom productionUrl resolver and default configuration for new documents. Read more about the document API.

  • form

    object | SanityFormConfig

    Extensions and customizations to the Studio forms. Accepts configurations for image and file asset sources as well as custom components to override the default Studio rendering. Read more about the form API.

  • plugins

    array | PluginOptions[]

    Studio plugins: takes an array of plugin declarations that can be called with or without a configuration object. Read more about plugins.

  • tools

    array | Tool[]

    Studio tools: takes an array of tool declarations that can be called with or without a configuration object. Read more about the tool API.

  • schema

    object | SchemaPluginOptions

    Schema definition: takes an array of types and an optional array of templates (initial value templates). While defining a schema is not required, there are few things inside the Studio that work without one. Read more about the schema API.

  • studio

    object | StudioComponentsPluginOptions

    Accepts a components object which will let you override the default rendering of certain bits of the studio UI. Read more about studio components.

  • Accepts a callback function containing an error: Error and an errorInfo: ErrorInfo arguments. Commonly used by plugin developers to implement customized error handling, external logging, and telemetry.

A complete list of plugin options is available in the type reference documentation.

Handling errors

As of sanity@v6.4.0, plugin and tool authors get a new @beta hook, useStudioErrorHandler(), for delegating unrecoverable request errors to the Studio's shared error UI instead of reinventing it. The developer handles what they can locally (inline errors, toasts, fallbacks) and delegates what they can't.

It exposes two methods, both delegating to the same native Studio Error UI.

attempt(fn, options?): for retryable requests

Wraps a request so the dialog's Try again re-invokes it. Resolves with the first successful attempt; the fn receives the attempt number.

import {useEffect, useState} from 'react'
import {useStudioErrorHandler, useClient} from 'sanity'

function MemberList() {
  const client = useClient({apiVersion: '2025-02-19'})
  const {attempt} = useStudioErrorHandler()
  const [members, setMembers] = useState<Member[]>()

  useEffect(() => {
    attempt(
      // Each invocation issues a *fresh* request — this is what
      // "Try again" re-runs.
      (attemptNumber) => {
        if (attemptNumber > 1) console.debug(`retry #${attemptNumber}`)
        return client.fetch<Member[]>('*[_type == "member"]')
      },
      {retryable: true},
    ).then(setMembers)
  }, [attempt, client])

  // ...render `members`
}

Gotcha

// ✓ fresh request per attempt
attempt(() => client.fetch(query), {retryable: true})

// ✗ request already fired; retry does nothing useful
const promise = client.fetch(query)
attempt(() => promise, {retryable: true})

attempt() also accepts an observable, but this observable must complete. Don't pass client.listen() or a long-lived Subject, as that would never resolve.

// ✓ single-shot request observable
await attempt(() => client.observable.fetch(query), {retryable: true})

handle(err): for non-retryable rejections

A drop-in promise rejection handler: pass it directly to .catch(handle). Claimable errors surface the Studio error dialog and leave the promise pending; unclaimable errors are re-thrown to the next .catch(), so downstream handlers still see caller-domain errors like validation failures, permission errors, and 404s.

Because handle receives an error rather than a re-runnable request, the dialog can't offer Try again. Use attempt() when the request is safe to re-run.

client
  .create(doc)
  .catch(handle)
  .catch((err) => {
    // Caller-domain errors (validation, permissions, 404, ...)
  })

Was this page helpful?