Skip to main content
Plugins are the primary way to extend and customize Sanity Studio. They can add schema types, tools, document actions, form components, and much more through a composable API.

What can plugins do?

Plugins can extend Studio in multiple ways:
  • Add schema types - Define new document and object types
  • Register tools - Add top-level navigation views
  • Customize document actions - Add publish, delete, or custom actions
  • Add document badges - Display status indicators
  • Add document inspectors - Create side panels for document inspection
  • Customize form components - Override input components
  • Modify Studio UI - Customize navbar, layout, and other components
  • Add i18n bundles - Provide translations

Creating a plugin

Use definePlugin to create a plugin:
import {definePlugin} from 'sanity'

export const myPlugin = definePlugin({
  name: 'my-plugin',
  
  schema: {
    types: [
      // Add custom schema types
    ],
  },
  
  tools: [
    // Add custom tools
  ],
  
  document: {
    actions: (prev, context) => {
      // Customize document actions
      return prev
    },
  },
})

Plugin with options

Create configurable plugins by accepting options:
import {definePlugin, type PluginOptions} from 'sanity'

interface MyPluginOptions {
  apiKey: string
  enabled?: boolean
}

export const myPlugin = definePlugin<MyPluginOptions>((options) => {
  const {apiKey, enabled = true} = options
  
  if (!enabled) {
    return {name: 'my-plugin'}
  }
  
  return {
    name: 'my-plugin',
    
    schema: {
      types: [
        {
          name: 'myType',
          type: 'object',
          fields: [
            {
              name: 'field',
              type: 'string',
            },
          ],
        },
      ],
    },
  }
})
Usage:
import {defineConfig} from 'sanity'
import {myPlugin} from './my-plugin'

export default defineConfig({
  // ...
  plugins: [
    myPlugin({
      apiKey: 'your-api-key',
      enabled: true,
    }),
  ],
})

Adding schema types

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

export const productPlugin = definePlugin({
  name: 'product-plugin',
  
  schema: {
    types: [
      defineType({
        name: 'product',
        type: 'document',
        title: 'Product',
        fields: [
          defineField({
            name: 'name',
            type: 'string',
            title: 'Product Name',
          }),
          defineField({
            name: 'price',
            type: 'number',
            title: 'Price',
          }),
        ],
      }),
    ],
  },
})

Adding tools

Tools are top-level views in the Studio navigation:
import {definePlugin} from 'sanity'
import {RocketIcon} from '@sanity/icons'
import {MyToolComponent} from './MyToolComponent'

export const myPlugin = definePlugin({
  name: 'my-plugin',
  
  tools: [
    {
      name: 'my-tool',
      title: 'My Tool',
      icon: RocketIcon,
      component: MyToolComponent,
    },
  ],
})
The tool component receives the tool configuration:
import {type Tool} from 'sanity'

export function MyToolComponent({tool}: {tool: Tool}) {
  return (
    <div>
      <h1>{tool.title}</h1>
      <p>This is my custom tool!</p>
    </div>
  )
}

Customizing document actions

Modify document actions based on context:
import {definePlugin} from 'sanity'

export const myPlugin = definePlugin({
  name: 'my-plugin',
  
  document: {
    actions: (prev, context) => {
      // Remove unpublish for specific document types
      if (context.schemaType === 'settings') {
        return prev.filter(({action}) => action !== 'unpublish')
      }
      
      // Add custom action
      return [...prev, MyCustomAction]
    },
  },
})

// Custom action component
function MyCustomAction(props) {
  return {
    label: 'Custom Action',
    onHandle: () => {
      console.log('Custom action executed!')
    },
  }
}

Adding document badges

Badges display status information in document lists:
import {definePlugin} from 'sanity'

export const myPlugin = definePlugin({
  name: 'my-plugin',
  
  document: {
    badges: (prev, context) => {
      // Add custom badge for featured documents
      if (context.schemaType === 'post') {
        return [...prev, FeaturedBadge]
      }
      return prev
    },
  },
})

function FeaturedBadge(props) {
  const {draft, published} = props
  const doc = draft || published
  
  if (!doc?.featured) return null
  
  return {
    label: 'Featured',
    color: 'primary',
  }
}

Adding document inspectors

Inspectors provide side panels for additional information:
import {definePlugin} from 'sanity'

export const myPlugin = definePlugin({
  name: 'my-plugin',
  
  document: {
    inspectors: (prev) => [
      ...prev,
      {
        name: 'custom-inspector',
        title: 'Custom Info',
        component: CustomInspector,
      },
    ],
  },
})

function CustomInspector(props) {
  const {documentId, documentType} = props
  
  return (
    <div>
      <h2>Custom Inspector</h2>
      <p>Document ID: {documentId}</p>
      <p>Type: {documentType}</p>
    </div>
  )
}

Customizing form components

Override input components for specific field types:
import {definePlugin} from 'sanity'
import {CustomStringInput} from './CustomStringInput'

export const myPlugin = definePlugin({
  name: 'my-plugin',
  
  form: {
    components: {
      input: (props) => {
        // Use custom component for string fields
        if (props.schemaType.name === 'string') {
          return <CustomStringInput {...props} />
        }
        // Use default for other types
        return props.renderDefault(props)
      },
    },
  },
})

Customizing Studio UI

Modify Studio’s shell components:
import {definePlugin} from 'sanity'
import {CustomNavbar} from './CustomNavbar'

export const myPlugin = definePlugin({
  name: 'my-plugin',
  
  studio: {
    components: {
      navbar: CustomNavbar,
      // Other components: layout, logo, toolMenu
    },
  },
})

Adding translations

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

const myPluginLocale = {
  locale: 'en-US',
  messages: {
    'my-plugin.action.label': 'Custom Action',
    'my-plugin.field.title': 'Custom Field',
  },
}

export const myPlugin = definePlugin({
  name: 'my-plugin',
  
  i18n: {
    bundles: [myPluginLocale],
  },
})

Composing multiple plugins

Plugins can include other plugins:
import {definePlugin} from 'sanity'
import {structureTool} from 'sanity/structure'
import {visionTool} from '@sanity/vision'

export const myPluginSuite = definePlugin({
  name: 'my-plugin-suite',
  
  plugins: [
    structureTool(),
    visionTool(),
  ],
  
  schema: {
    types: [
      // Additional types
    ],
  },
})

Real-world plugin examples

Color input plugin

import {definePlugin, defineType} from 'sanity'
import {ColorInput} from './ColorInput'

export const colorInput = definePlugin({
  name: 'color-input',
  
  schema: {
    types: [
      defineType({
        name: 'color',
        type: 'object',
        title: 'Color',
        fields: [
          {
            name: 'hex',
            type: 'string',
            title: 'Hex',
          },
          {
            name: 'alpha',
            type: 'number',
            title: 'Alpha',
          },
        ],
        components: {
          input: ColorInput,
        },
      }),
    ],
  },
})

Analytics dashboard tool

import {definePlugin} from 'sanity'
import {ChartIcon} from '@sanity/icons'
import {AnalyticsDashboard} from './AnalyticsDashboard'

export const analyticsPlugin = definePlugin<{apiKey: string}>((options) => {
  return {
    name: 'analytics-plugin',
    
    tools: [
      {
        name: 'analytics',
        title: 'Analytics',
        icon: ChartIcon,
        component: (props) => (
          <AnalyticsDashboard apiKey={options.apiKey} {...props} />
        ),
      },
    ],
  }
})

Workflow plugin

import {definePlugin} from 'sanity'

export const workflowPlugin = definePlugin({
  name: 'workflow-plugin',
  
  document: {
    actions: (prev, context) => {
      // Add workflow actions based on document status
      const doc = context.draft || context.published
      const status = doc?.workflowStatus
      
      if (status === 'in-review') {
        return [
          ...prev,
          ApproveAction,
          RejectAction,
        ]
      }
      
      return prev
    },
    
    badges: (prev, context) => {
      return [...prev, WorkflowStatusBadge]
    },
  },
})

Plugin best practices

  • Namespace your types - Use prefixes to avoid conflicts (e.g., myPlugin.field)
  • Make plugins configurable - Accept options for flexibility
  • Document your plugin - Provide clear README and TypeScript types
  • Test thoroughly - Test with different Studio configurations
  • Follow conventions - Use Sanity UI components and patterns
  • Don’t modify global state directly
  • Avoid blocking the main thread with heavy computations
  • Don’t assume specific schema types exist
  • Be careful with plugin load order dependencies

Publishing plugins

To share your plugin:
  1. Create an npm package
  2. Export your plugin using definePlugin
  3. Include TypeScript types
  4. Add documentation
  5. Publish to npm
// package.json
{
  "name": "sanity-plugin-my-plugin",
  "version": "1.0.0",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "peerDependencies": {
    "sanity": "^3.0.0"
  }
}

Build docs developers (and LLMs) love