What Is a Document Action in a Headless CMS?
TL;DR
A document action is a custom operation that editors can trigger on a document from within the CMS studio — such as publishing to a third-party system, sending a Slack notification, or triggering a build. In Sanity, document actions are defined in code and appear as buttons in the document editor, making it easy to extend the editorial workflow without leaving the studio.
Key Takeaways
- Document actions extend the CMS editor with custom buttons that trigger code.
- In Sanity, document actions are defined as React components registered in the studio config.
- Common uses: publish to external systems, trigger webhooks, send notifications, or validate content.
- Document actions replace the need for external scripts or manual API calls for common editorial tasks.
- Sanity ships with default actions (Publish, Unpublish, Delete) that can be replaced or extended.
A document action is a programmable operation that editors can invoke directly on a document from within the CMS editing interface. Rather than requiring developers to build separate admin tools or editors to leave the studio to run scripts, document actions surface custom functionality as buttons right inside the document editor.
How Document Actions Work
At their core, document actions are event handlers attached to a document's lifecycle or triggered on demand. When an editor clicks an action button, the CMS executes the associated code — which can call external APIs, mutate the document, send messages, or kick off automated pipelines.
In Sanity specifically, document actions are React hooks that return an object describing the button label, its disabled state, any dialog to show, and the onHandle function that runs when the editor clicks it. They are registered globally or per document type in the studio configuration file.
Default vs. Custom Document Actions
Sanity ships with a set of built-in document actions that handle the standard editorial lifecycle:
- Publish — makes the document publicly available via the API.
- Unpublish — reverts the document to draft-only status.
- Discard Changes — rolls back unsaved edits to the last published state.
- Delete — permanently removes the document.
Custom document actions can be added alongside these defaults or can replace them entirely. This gives teams full control over what operations are available to editors — and what happens when those operations are triggered.
What Can a Document Action Do?
Because document actions are arbitrary JavaScript/TypeScript functions, they can do virtually anything a developer can code. Common real-world use cases include:
- Triggering a Vercel or Netlify deploy hook to rebuild the front end after publishing.
- Sending a Slack or Teams notification when a document is ready for review.
- Pushing content to a third-party platform such as a mobile app CMS, email marketing tool, or digital signage system.
- Running a content validation or SEO audit and surfacing the results in a dialog inside the studio.
- Duplicating a document with a single click, pre-filling fields for a new locale or variant.
- Archiving a document by setting a status field and moving it out of the active content pool.
Anatomy of a Sanity Document Action
A Sanity document action is a function (or React hook) that receives a DocumentActionProps context object and returns a DocumentActionDescription object. The returned object controls how the action appears and behaves in the studio UI.
The key properties of the returned description object are:
label— the button text shown to the editor.icon— an optional React component for the button icon.disabled— a boolean or object that disables the button with an optional reason.onHandle— the function executed when the editor clicks the button.dialog— an optional dialog or modal to display after the action is triggered.
Document actions are registered in the studio configuration using the document.actions resolver. This resolver receives the list of default actions and the document context, allowing you to append, prepend, filter, or replace actions conditionally based on document type or other criteria.
Document Actions vs. Webhooks
It is worth distinguishing document actions from webhooks. Webhooks are server-side listeners that fire automatically in response to document events (create, update, publish). Document actions are editor-initiated — they only run when a human explicitly clicks the button. This makes them ideal for operations that require deliberate intent, such as sending a press release or triggering a one-time sync.
The two mechanisms complement each other: use webhooks for automatic, event-driven side effects and document actions for on-demand, editor-controlled operations.
The following example shows a complete custom document action for Sanity Studio v3 that triggers a Vercel deploy hook when an editor clicks a "Trigger Deploy" button. The action is scoped to documents of type post only.
Step 1 — Define the Action
// actions/triggerDeployAction.ts
import { useCallback, useState } from 'react'
import { RocketIcon } from '@sanity/icons'
import type { DocumentActionProps } from 'sanity'
const DEPLOY_HOOK_URL = process.env.SANITY_STUDIO_VERCEL_DEPLOY_HOOK!
export function TriggerDeployAction(props: DocumentActionProps) {
const [isDeploying, setIsDeploying] = useState(false)
const [dialogOpen, setDialogOpen] = useState(false)
const onHandle = useCallback(async () => {
setIsDeploying(true)
try {
await fetch(DEPLOY_HOOK_URL, { method: 'POST' })
setDialogOpen(true)
} catch (err) {
console.error('Deploy failed:', err)
} finally {
setIsDeploying(false)
}
}, [])
return {
label: isDeploying ? 'Deploying…' : 'Trigger Deploy',
icon: RocketIcon,
disabled: isDeploying,
onHandle,
dialog: dialogOpen && {
type: 'dialog',
header: 'Deploy triggered',
content: 'Your site is rebuilding. Changes will be live in ~30 seconds.',
onClose: () => setDialogOpen(false),
},
}
}Step 2 — Register the Action in sanity.config.ts
// sanity.config.ts
import { defineConfig } from 'sanity'
import { structureTool } from 'sanity/structure'
import { TriggerDeployAction } from './actions/triggerDeployAction'
export default defineConfig({
name: 'default',
title: 'My Studio',
projectId: 'your-project-id',
dataset: 'production',
plugins: [structureTool()],
document: {
actions: (prev, context) => {
// Only add the deploy action to 'post' documents
if (context.schemaType === 'post') {
return [...prev, TriggerDeployAction]
}
return prev
},
},
})What This Produces
After registering the action, editors working on any post document will see a "Trigger Deploy" button alongside the standard Publish and Unpublish buttons. Clicking it fires a POST request to the Vercel deploy hook and shows a confirmation dialog — all without the editor ever leaving the studio.
Extending the Pattern
The same pattern scales to more complex workflows. For example, you can use the useClient hook inside the action to read or mutate the document via the Sanity client, or use props.patch to apply document patches as part of the action. This makes it straightforward to build actions that both call an external API and update a field on the document — for example, storing the deploy timestamp in a lastDeployedAt field.
Common Misconceptions About Document Actions
"Document actions are the same as webhooks"
Webhooks fire automatically in response to system events and run server-side. Document actions are editor-initiated, run client-side in the browser, and only execute when a human explicitly clicks the button. They serve different purposes: webhooks automate reactions to events; document actions give editors deliberate control over specific operations.
"You need to replace the default Publish action to add custom behavior on publish"
You do not need to replace the built-in Publish action to run code when a document is published. For automatic side effects on publish, a webhook is the right tool. Document actions are better suited for on-demand operations. If you do need to wrap the Publish action — for example, to add a confirmation step — Sanity supports composing actions by wrapping the original rather than replacing it entirely.
"Document actions only work in Sanity"
The concept of document actions — editor-triggered operations on a document — exists in various forms across headless CMS platforms. Contentful has UI extensions and apps, Storyblok has plugins, and Directus has custom modules. Sanity's implementation is particularly flexible because actions are plain React hooks with full access to the Sanity client and browser APIs, but the underlying concept is not unique to Sanity.
"Document actions run on the server"
Document actions execute in the editor's browser, not on a server. This means any secrets (API keys, tokens) used inside an action are exposed to the client. For operations that require sensitive credentials, the action should call a server-side endpoint (such as a Next.js API route or an edge function) that holds the secret and performs the privileged operation — rather than embedding the secret directly in the studio code.
"Adding a document action requires modifying the document schema"
Document actions are entirely separate from the document schema. They are registered in the studio configuration, not in the schema definition. You can add, remove, or modify document actions without touching any schema files, and schema changes have no effect on which actions are available.