Document actions

Introduction to building custom document actions.

You can customize both document actions and badges

Get started

To set up a new custom action component you need to complete the following steps:

  1. Define an action component
  2. Register the action component

In this example we'll make an action component that will display an alert window when clicked.

1. Define a document action component

First, create a file in your local Studio for your action. Let's call the component HelloWorldAction and put it in a file called HelloWorldAction.js.

Learn about the complete Document Actions API

// HelloWorldAction.js

export function HelloWorldAction(props) { 
  return {
    label: 'Hello world',
    onHandle: () => {
      // Here you can perform your actions
      window.alert('đź‘‹ Hello from custom action')
    }    
  } 
}

2. Register and resolve document actions

Now that you have defined a document action, it can be registered by making an implementation of the part part:@sanity/base/document-actions/resolver by creating a file that we will call resolveDocumentActions.js. This file must by registered in your Studio's sanity.json's parts array like this:

// sanity.json 
//... 
"parts": [ 
  //... 
  { 
    "implements": "part:@sanity/base/document-actions/resolver", 
    "path": "resolveDocumentActions.js" 
  } 
]

The document action resolver provides you with a central place to make decisions about the collection of actions enabled for a given document state. For example, this is where you can decide on the order in which the actions will appear and disable / enable actions based on values in the document.

The implementation of the part:@sanity/base/document-actions/resolver must export a function that is called with an argument of the same type as the action component is called with, DocumentActionProps, and return an array of enabled actions.

Let's add the HelloWorldAction to the list of default document actions. First you need to import both our HelloWorldAction and the default document actions, then return an array where you spread the default document actions and include the HelloWorldAction, like this:

// resolveDocumentActions.js

// import the default document actions
import defaultResolve from 'part:@sanity/base/document-actions'

import {HelloWorldAction} from './HelloWorldAction'

export default function resolveDocumentActions(props) {
  return [...defaultResolve(props), HelloWorldAction]
}

Since changes were made to sanity.json file you will have to restart the Studio server to see the changes (ctrl + c and sanity start).

The Hello World action is available in the document action menu

Typical use cases

Showing actions conditionally

In some situations, a document action may not be relevant, and instead of making it disabled, you rather want it to not appear at all. For example, some document actions may only be relevant for certain types. In these cases, check the condition and return null from the action component if you want to hide the action.

Here's an example of an imaginary "spellcheck" action that will only appear in menus for documents of type article:

export function SpellCheckArticleAction(props) {
  if (props.type !== 'article') {
   return null
  }
	return {
		label: 'Spellcheck article'
    //...
  }
}

Update a value then publish document

Usually a document action provides a way for the user to manipulate the document. To get access to operations that can be done on a document, you can use the useDocumentOperation hook from the @sanity/react-hooks.

Learn about the Studio React Hooks →

import {useDocumentOperation} from '@sanity/react-hooks'

This will give you access to a set of operations that the current document supports. Each operation comes with disabled prop and an execute method.

In this example we update the publishedAt value of a document before we publish it. We also provide feedback to the user about the progress of the operation.

// setAndPublishAction.js

import {useState, useEffect} from 'react'
import {useDocumentOperation} from '@sanity/react-hooks'

export default function SetAndPublishAction(props) {
  const {patch, publish} = useDocumentOperation(props.id, props.type)
  const [isPublishing, setIsPublishing] = useState(false)
  
  useEffect(() => {
    // if the isPublishing state was set to true and the draft has changed
    // to become `null` the document has been published
    if (isPublishing && !props.draft) {
      setIsPublishing(false)
    }
  }, [props.draft])
  
  return {
    disabled: publish.disabled,
    label: isPublishing ? 'Publishing…' : 'Publish',
    onHandle: () => {
      // This will update the button text 
      setIsPublishing(true)
      
      // Set publishedAt to current date and time
      patch.execute([{set: {publishedAt: new Date().toISOString()}}])
      
      // Perform the publish
      publish.execute()
      
      // Signal that the action is completed
      props.onComplete() 
    }
  }
}

Note: due to current technical limitation the only way to check for whether the publish action has completed is to check for the draft being null after publish action was invoked. We are working on improving this in the future.

Selectively replacing builtin actions

Sometimes you may want to replace only a single or a few of the default document actions (publish, duplicate, delete) in your Studio instance. Here's an example of how to replace the builtin publish action with your own:

// resolveDocumentActions.js

import defaultResolve, {PublishAction} from 'part:@sanity/base/document-actions'
import {MyCustomPublishAction} from './src/MyCustomPublishAction'

export default function resolveDocumentActions(props) {
  return defaultResolve(props)
  .map(Action =>
    Action === PublishAction ? MyCustomPublishAction : Action
  )
}

And here's an example of how you can selectively import and add builtin actions to your own set of document actions:

// resolveDocumentActions.js

import {DeleteAction, DuplicateAction} from 'part:@sanity/base/document-actions'
import {CustomPublishAction} from './src/MyCustomPublishAction'

export default function resolveDocumentActions(props) {
  return [CustomPublishAction, DeleteAction, DuplicateAction]
}

Stateful action components and dialog flows

You can think about the action component as a functional React component and you can use React hooks to give it internal state. This means an action can support all sorts of user interaction, including dialogs. Here's an example of an action that let's the user edit the title from a dropdown:

import React from 'react'
import {useDocumentOperation} from '@sanity/react-hooks'

export function DialogAction({id, type, published, draft}) {
  const doc = draft || published

  const [isDialogOpen, setDialogOpen] = React.useState(false)

  const {patch} = useDocumentOperation(id, type)

  return {
    label: `Edit title`,
    onHandle: () => {
      setDialogOpen(true)
    },
    dialog: isDialogOpen && {
      type: 'modal',
      onClose: () => {
        setDialogOpen(false)
      },
      content: (
        <>
          <h2>Edit title field</h2>
          <input
            type="text"
            value={(doc && doc.title) || ''}
            onChange={event =>
             patch.execute([
							  {set: {'title': event.currentTarget.value}}
              ])}
          />
          <button onClick={() => setDialogOpen(false)}>Done</button>
        </>
      )
    }
  }
}

You can learn more and read about the different kinds of dialogs supported in the Document actions API documentation.

Was this article helpful?