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
Start by importing the definePlugin helper:
import { definePlugin } from 'sanity'
Create a basic plugin structure:
export const myPlugin = definePlugin ({
name: 'my-plugin' ,
// Add schema types
schema: {
types: [
// your custom types
],
},
// Add tools
tools: [
// your custom tools
],
})
Add it to your studio configuration:
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' },
],
},
],
}),
],
},
})
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 >
),
},
},
})
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
Configure your plugin package:
{
"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"
}
}
Compile TypeScript and bundle your plugin:
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