Skip to main content
Plugins are the primary way to extend Sanity Studio with reusable functionality. They can add schema types, tools, document actions, form components, and more.

Plugin architecture

A Sanity plugin is a function that returns a configuration object. Plugins can:
  • Add schema types
  • Register tools (top-level views)
  • Customize document actions and badges
  • Add form components
  • Modify studio components
  • Provide internationalization bundles

Creating your first plugin

1
Import definePlugin
2
Start by importing the definePlugin helper:
3
import {definePlugin} from 'sanity'
4
Define your plugin
5
Create a basic plugin structure:
6
export const myPlugin = definePlugin({
  name: 'my-plugin',
  
  // Add schema types
  schema: {
    types: [
      // your custom types
    ],
  },
  
  // Add tools
  tools: [
    // your custom tools
  ],
})
7
Use your plugin
8
Add it to your studio configuration:
9
import {defineConfig} from 'sanity'
import {myPlugin} from './plugins/myPlugin'

export default defineConfig({
  // ... project settings
  plugins: [myPlugin()],
})

Plugin with options

Create configurable plugins that accept options:
import {definePlugin, type PluginOptions} from 'sanity'

interface MyPluginConfig {
  apiKey?: string
  enabled?: boolean
}

export const myPlugin = definePlugin<MyPluginConfig>((options) => {
  const {apiKey, enabled = true} = options || {}
  
  if (!enabled) {
    return {
      name: 'my-plugin',
    }
  }
  
  return {
    name: 'my-plugin',
    
    schema: {
      types: [
        // schema types that use apiKey
      ],
    },
  }
})
Use it with options:
export default defineConfig({
  plugins: [
    myPlugin({
      apiKey: 'your-api-key',
      enabled: true,
    }),
  ],
})

Real-world plugin example

Here’s how the Vision plugin is implemented:
import {definePlugin, type PluginOptions} from 'sanity'
import {EyeOpenIcon} from '@sanity/icons'
import {route} from 'sanity/router'

interface VisionToolConfig {
  name?: string
  title?: string
  icon?: ComponentType
}

export const visionTool = definePlugin<VisionToolConfig | void>((options) => {
  const {name, title, icon, ...config} = options || {}
  
  return {
    name: '@sanity/vision',
    
    tools: [
      {
        name: name || 'vision',
        title: title || 'Vision',
        icon: icon || EyeOpenIcon,
        component: lazy(() => import('./SanityVision')),
        options: config,
        router: route.create('/*'),
      },
    ],
    
    i18n: {
      bundles: [visionUsEnglishLocaleBundle],
    },
  }
})

Adding schema types

Plugins can contribute schema types:
import {definePlugin, defineType} from 'sanity'

export const colorPlugin = definePlugin({
  name: 'color-plugin',
  
  schema: {
    types: [
      defineType({
        name: 'color',
        type: 'object',
        title: 'Color',
        fields: [
          {
            name: 'hex',
            type: 'string',
            title: 'Hex code',
          },
          {
            name: 'rgb',
            type: 'object',
            fields: [
              {name: 'r', type: 'number'},
              {name: 'g', type: 'number'},
              {name: 'b', type: 'number'},
            ],
          },
        ],
      }),
    ],
  },
})

Creating custom tools

Tools are top-level views in the studio:
import {definePlugin} from 'sanity'
import {RocketIcon} from '@sanity/icons'
import {route} from 'sanity/router'

function MyToolComponent() {
  return (
    <div>
      <h1>My Custom Tool</h1>
      {/* Tool content */}
    </div>
  )
}

export const myToolPlugin = definePlugin({
  name: 'my-tool-plugin',
  
  tools: [
    {
      name: 'my-tool',
      title: 'My Tool',
      icon: RocketIcon,
      component: MyToolComponent,
      router: route.create('/*'),
    },
  ],
})
Tools appear in the main navigation bar and have their own URL routes.

Customizing document actions

Add custom document actions through plugins:
import {definePlugin, type DocumentActionComponent} from 'sanity'

const myCustomAction: DocumentActionComponent = (props) => {
  return {
    label: 'Custom action',
    icon: StarIcon,
    onHandle: () => {
      // Handle action
    },
  }
}

export const actionsPlugin = definePlugin({
  name: 'actions-plugin',
  
  document: {
    actions: (prev, context) => {
      // Add action to all documents
      return [...prev, myCustomAction]
    },
  },
})

Adding studio components

Customize studio UI components:
import {definePlugin, type LayoutProps} from 'sanity'

export const studioComponentsPlugin = definePlugin({
  name: 'studio-components-plugin',
  
  studio: {
    components: {
      layout: (props: LayoutProps) => (
        <Box height="fill">
          <Banner>Custom banner</Banner>
          {props.renderDefault(props)}
        </Box>
      ),
      navbar: (props) => (
        <Box>
          {props.renderDefault(props)}
        </Box>
      ),
    },
  },
})

Form components in plugins

Provide custom input components:
import {definePlugin} from 'sanity'

function ColorPickerInput(props) {
  return (
    <div>
      <input
        type="color"
        value={props.value || '#000000'}
        onChange={(e) => props.onChange(e.target.value)}
      />
    </div>
  )
}

export const colorInputPlugin = definePlugin({
  name: 'color-input-plugin',
  
  form: {
    components: {
      input: (props) => {
        if (props.schemaType.name === 'colorPicker') {
          return <ColorPickerInput {...props} />
        }
        return props.renderDefault(props)
      },
    },
  },
})

Plugin composition

Plugins can include other plugins:
import {definePlugin} from 'sanity'
import {colorPlugin} from './color-plugin'
import {myToolPlugin} from './my-tool-plugin'

export const suitePlugin = definePlugin({
  name: 'suite-plugin',
  
  plugins: [
    colorPlugin(),
    myToolPlugin(),
  ],
  
  // Additional configuration
  schema: {
    types: [
      // Additional types
    ],
  },
})

Internationalization in plugins

Provide translations for your plugin:
import {definePlugin} from 'sanity'

const myPluginLocaleBundle = {
  locale: 'en-US',
  namespace: 'myPlugin',
  resources: {
    'action.publish': 'Publish now',
    'action.schedule': 'Schedule',
  },
}

export const i18nPlugin = definePlugin({
  name: 'i18n-plugin',
  
  i18n: {
    bundles: [myPluginLocaleBundle],
  },
})

Plugin validation

The definePlugin helper validates your configuration:
  • No projectId or dataset: Plugins cannot specify these (workspace-level only)
  • Valid plugin options: Options must match the expected structure
  • Schema types: All schema types must be valid
  • Tools: Tool definitions must include required fields

Testing your plugin

Create a test studio to develop your plugin:
// test-studio/sanity.config.ts
import {defineConfig} from 'sanity'
import {myPlugin} from '../src/index'

export default defineConfig({
  projectId: 'test',
  dataset: 'test',
  
  plugins: [
    myPlugin({
      // test configuration
    }),
  ],
})
Use the dev studio in your plugin repository to test functionality during development.

Publishing your plugin

1
Prepare package.json
2
Configure your plugin package:
3
{
  "name": "sanity-plugin-my-plugin",
  "version": "1.0.0",
  "description": "My awesome Sanity plugin",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "peerDependencies": {
    "sanity": "^3.0.0"
  }
}
4
Build your plugin
5
Compile TypeScript and bundle your plugin:
6
npm run build
7
Publish to npm
8
Publish your plugin:
9
npm publish

Plugin best practices

  • Use unique names: Prefix plugin names with your organization or username
  • Accept options: Make plugins configurable when possible
  • Provide TypeScript types: Export proper types for better DX
  • Document your plugin: Include README with usage examples
  • Compose when appropriate: Break large plugins into smaller, composable ones
  • Test thoroughly: Create test studios to verify functionality

Next steps

Build docs developers (and LLMs) love