How to prevent second plugin from overwriting first document action?

13 replies
Last updated: Jan 28, 2026
Hello, I am about to create a second plugin to add a document action. How can I make sure the second plugin does not overwrite the first plugin?
I used this documentation to create the first one:
https://www.sanity.io/docs/document-actions
It seems there is no way to reference the previously implemented
sanity/base/document-actions/resolver
part. I might have missed some documentation, so any pointers would be helpful.
AI Update

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.

Show original thread
13 replies

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.

Was this answer helpful?