How to add multiple custom document actions in Sanity plugins
In Sanity Studio v3, you don't need to worry about plugins overwriting each other's document actions - the system handles composition automatically! When you add multiple plugins or define multiple actions, they all get combined into the actions menu.
Here's how it works:
Adding Multiple Actions
When you register document actions in your sanity.config.ts, you can provide either a static array or a resolver function. If you want to add actions from multiple sources (your own custom actions plus the defaults), use the resolver function approach:
import {defineConfig} from 'sanity'
import {FirstCustomAction} from './first-action'
import {SecondCustomAction} from './second-action'
export default defineConfig({
// ... rest of config
document: {
actions: (prev, context) => {
// prev contains all previously registered actions (from plugins and defaults)
// Just add your new actions to the array
return [FirstCustomAction, SecondCustomAction, ...prev]
},
},
})The prev parameter contains all actions that have been registered before yours - including actions from plugins and the Studio defaults (publish, duplicate, delete, etc.). By spreading ...prev into your returned array, you preserve all existing actions while adding your new ones.
Multiple Plugins with Actions
If you're creating separate plugins that each add document actions, they compose automatically:
// first-plugin.ts
import {definePlugin} from 'sanity'
export const firstPlugin = definePlugin({
name: 'first-plugin',
document: {
actions: (prev) => [MyFirstAction, ...prev]
}
})
// second-plugin.ts
export const secondPlugin = definePlugin({
name: 'second-plugin',
document: {
actions: (prev) => [MySecondAction, ...prev]
}
})
// sanity.config.ts
export default defineConfig({
plugins: [
firstPlugin(),
secondPlugin(), // These compose automatically!
]
})Each plugin receives the actions from previous plugins through the prev parameter, so they naturally compose without overwriting each other.
Key Differences from Studio v2
This is much simpler than Studio v2's parts system (which used part:@sanity/base/document-actions/resolver). In v2, you had to manually import the default resolver using the parts system:
// Studio v2 approach (deprecated)
import defaultResolve from "part:@sanity/base/document-actions"
export default function resolveDocumentActions(props) {
return [...defaultResolve(props), MyCustomAction]
}Studio v3 eliminates this complexity - you just work with the prev parameter in your resolver function, and the framework handles all the composition for you.
Check out the document actions documentation for more examples, including how to conditionally show actions, extend built-in actions, and create actions with custom dialogs.
Sanity – Build the way you think, not the way your CMS thinks
Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.