Skip to main content
Document actions are the operations available in the document editor, such as publish, delete, and duplicate. You can create custom actions to extend document workflows.

What are document actions?

Document actions appear in the document toolbar and provide operations like:
  • Publishing and unpublishing documents
  • Deleting and duplicating documents
  • Custom workflows (approval, scheduling, etc.)
  • Integration with external services

Understanding the action component

Actions are React components that return action descriptions:
import {type DocumentActionComponent} from 'sanity'

const myAction: DocumentActionComponent = (props) => {
  return {
    label: 'My Action',
    icon: RocketIcon,
    onHandle: () => {
      // Handle the action
    },
  }
}

Creating your first action

1
Define the action component
2
Create a basic action:
3
import {CheckmarkIcon} from '@sanity/icons'
import {type DocumentActionComponent} from 'sanity'

const approveAction: DocumentActionComponent = (props) => {
  const {draft, published} = props
  
  return {
    label: 'Approve',
    icon: CheckmarkIcon,
    tone: 'positive',
    onHandle: () => {
      // Handle approval logic
      console.log('Approving document:', draft?._id || published?._id)
    },
  }
}
4
Register the action
5
Add it to your studio configuration:
6
import {defineConfig} from 'sanity'

export default defineConfig({
  document: {
    actions: (prev, context) => {
      // Add to all documents
      return [...prev, approveAction]
    },
  },
})
7
Add conditional logic
8
Show actions only for specific document types:
9
export default defineConfig({
  document: {
    actions: (prev, context) => {
      if (context.schemaType === 'post') {
        return [...prev, approveAction]
      }
      return prev
    },
  },
})

Action description properties

Actions return a description object with these properties:
interface DocumentActionDescription {
  label: string                    // Button label
  icon?: ComponentType | ReactNode // Icon component
  title?: ReactNode               // Tooltip text
  tone?: ButtonTone               // Visual style
  disabled?: boolean              // Disable the action
  shortcut?: string               // Keyboard shortcut
  onHandle?: () => void          // Click handler
  dialog?: DocumentActionDialogProps // Dialog content
}

Built-in actions

Sanity provides these default actions:
  • publish - Publish the document
  • unpublish - Unpublish the document
  • delete - Delete the document
  • duplicate - Create a copy
  • discardChanges - Revert to published version
  • restore - Restore a deleted document

Replacing default actions

Replace a built-in action with a custom version:
import {type DocumentActionComponent} from 'sanity'

const MyPublishAction: DocumentActionComponent = (props) => {
  return {
    label: 'Publish Now',
    icon: RocketIcon,
    tone: 'primary',
    onHandle: async () => {
      // Custom publish logic
      const {patch, publish} = props
      
      // Add metadata before publishing
      patch.execute([
        {type: 'set', path: ['publishedBy'], value: 'current-user'},
        {type: 'set', path: ['publishedAt'], value: new Date().toISOString()},
      ])
      
      // Then publish
      publish.execute()
    },
  }
}

MyPublishAction.action = 'publish'

export default defineConfig({
  document: {
    actions: (prev) =>
      prev.map((action) =>
        action.action === 'publish' ? MyPublishAction : action
      ),
  },
})
Set the action property to identify which built-in action you’re replacing.

Actions with dialogs

Show confirmation dialogs before executing actions:
import {useState} from 'react'
import {type DocumentActionComponent} from 'sanity'

const deleteWithConfirm: DocumentActionComponent = (props) => {
  const [dialogOpen, setDialogOpen] = useState(false)
  
  return {
    label: 'Delete',
    icon: TrashIcon,
    tone: 'critical',
    onHandle: () => setDialogOpen(true),
    dialog: dialogOpen && {
      type: 'confirm',
      tone: 'critical',
      message: 'Are you sure you want to delete this document?',
      onCancel: () => setDialogOpen(false),
      onConfirm: () => {
        props.onComplete()
        // Execute delete logic
      },
    },
  }
}

Dialog types

Sanity supports multiple dialog types:
dialog: {
  type: 'confirm',
  tone: 'critical',
  message: 'Are you sure?',
  onConfirm: () => {
    // Handle confirmation
  },
  onCancel: () => {
    // Handle cancellation
  },
}

Using document props

Actions receive document state and utilities:
const myAction: DocumentActionComponent = (props) => {
  const {
    id,              // Document ID
    type,            // Document type
    draft,           // Draft document
    published,       // Published document
    patch,           // Patch API
    publish,         // Publish API
    unpublish,       // Unpublish API
    delete: del,     // Delete API (renamed to avoid keyword)
  } = props
  
  return {
    label: 'My Action',
    onHandle: () => {
      // Use document state
      if (draft) {
        console.log('Draft title:', draft.title)
      }
    },
  }
}

Async actions

Handle asynchronous operations:
import {useState} from 'react'

const exportAction: DocumentActionComponent = (props) => {
  const [isExporting, setIsExporting] = useState(false)
  
  return {
    label: isExporting ? 'Exporting...' : 'Export',
    icon: DownloadIcon,
    disabled: isExporting,
    onHandle: async () => {
      setIsExporting(true)
      
      try {
        const response = await fetch('/api/export', {
          method: 'POST',
          body: JSON.stringify(props.draft || props.published),
        })
        
        const blob = await response.blob()
        // Download file
        
      } finally {
        setIsExporting(false)
      }
    },
  }
}

Grouping actions

Organize actions into groups:
const myAction: DocumentActionComponent = (props) => {
  return {
    label: 'Custom Action',
    group: 'paneActions',
    onHandle: () => {},
  }
}
Available groups:
  • default - Main action bar
  • paneActions - Secondary actions menu

Conditional actions

Show actions based on document state:
const approveAction: DocumentActionComponent = (props) => {
  const {draft, published} = props
  const status = draft?.status || published?.status
  
  // Only show for pending documents
  if (status !== 'pending') {
    return null
  }
  
  return {
    label: 'Approve',
    icon: CheckmarkIcon,
    onHandle: () => {
      props.patch.execute([{
        type: 'set',
        path: ['status'],
        value: 'approved',
      }])
    },
  }
}

Keyboard shortcuts

Add keyboard shortcuts to actions:
const quickPublish: DocumentActionComponent = (props) => {
  return {
    label: 'Quick Publish',
    shortcut: 'Ctrl+Shift+P',
    onHandle: () => {
      props.publish.execute()
    },
  }
}

Integration with external APIs

Integrate with external services:
const notifySlack: DocumentActionComponent = (props) => {
  const [isSending, setIsSending] = useState(false)
  
  return {
    label: 'Notify Slack',
    icon: BellIcon,
    disabled: isSending,
    onHandle: async () => {
      setIsSending(true)
      
      try {
        await fetch('https://hooks.slack.com/services/YOUR/WEBHOOK/URL', {
          method: 'POST',
          body: JSON.stringify({
            text: `Document published: ${props.draft?.title}`,
          }),
        })
      } finally {
        setIsSending(false)
      }
    },
  }
}

Action metadata

Add metadata for debugging:
const myAction: DocumentActionComponent = (props) => {
  return {
    label: 'My Action',
    onHandle: () => {},
  }
}

myAction.displayName = 'MyCustomAction'

Testing actions

Test actions in isolation:
import {renderHook} from '@testing-library/react'

test('approve action', () => {
  const props = {
    id: 'test-id',
    type: 'post',
    draft: {_id: 'drafts.test-id', status: 'pending'},
    patch: {execute: jest.fn()},
  }
  
  const result = approveAction(props)
  
  expect(result?.label).toBe('Approve')
  expect(result?.onHandle).toBeDefined()
})

Best practices

  • Use descriptive labels: Make action purpose clear
  • Add icons: Visual cues improve UX
  • Handle loading states: Show feedback for async operations
  • Confirm destructive actions: Always confirm delete/unpublish
  • Check permissions: Verify user has required access
  • Provide feedback: Show success/error messages
Use the tone property to visually indicate action severity: positive, caution, critical, or primary.

Next steps

Build docs developers (and LLMs) love