Accessing document values in custom document actions and displaying custom error messages

12 replies
Last updated: Mar 14, 2021
Hey, can I access the document values in custom document actions? Like if want to create a custom "Publish" button where I want to access some of the documents values?
AI Update

Yes, you can absolutely access document values in custom document actions! The action component receives props that include both draft and published versions of the document.

Here's what you get access to in your custom document action props:

  • props.draft - The draft version of the document (if it exists)
  • props.published - The published version of the document (if it exists)
  • props.id - The document ID
  • props.type - The document schema type

Basic Example

Here's a simple custom publish action that accesses document values:

export function CustomPublishAction(props) {
  const doc = props.draft || props.published
  
  return {
    label: 'Publish',
    onHandle: () => {
      // Access any field from the document
      console.log('Title:', doc?.title)
      console.log('Slug:', doc?.slug?.current)
      
      // Your custom logic here
    }
  }
}

Real-World Example: Conditional Publishing

Here's a more practical example that checks document values before publishing:

import {useState, useEffect} from 'react'
import {useDocumentOperation} from 'sanity'

export function ConditionalPublishAction(props) {
  const {patch, publish} = useDocumentOperation(props.id, props.type)
  const [isPublishing, setIsPublishing] = useState(false)
  
  const doc = props.draft || props.published
  const hasRequiredFields = doc?.title && doc?.slug

  useEffect(() => {
    if (isPublishing && !props.draft) {
      setIsPublishing(false)
    }
  }, [props.draft])

  return {
    disabled: publish.disabled || !hasRequiredFields,
    label: isPublishing ? 'Publishing…' : 'Publish',
    onHandle: () => {
      setIsPublishing(true)
      
      // Set publishedAt based on document values
      if (!doc.publishedAt) {
        patch.execute([{set: {publishedAt: new Date().toISOString()}}])
      }
      
      publish.execute()
      props.onComplete()
    },
  }
}

Using with Dialogs

You can also use document values in dialog flows:

export function EditTitleAction({id, type, published, draft}) {
  const doc = draft || published
  const [documentTitle, setDocumentTitle] = useState(doc?.title)
  
  return {
    label: 'Edit title',
    dialog: {
      type: 'dialog',
      header: 'Edit Title',
      content: (
        <input
          value={documentTitle}
          onChange={(e) => setDocumentTitle(e.target.value)}
        />
      )
    }
  }
}

The key thing to remember is that props.draft and props.published give you full access to all document field values, so you can read any data you need to make decisions or display information in your custom actions.

For more details, check out the Document Actions documentation and the Document Actions API reference.

I just recently implemented document actions to set a readOnly hidden field of type
slug
with values from within the document including a field from a referenced document.
Here is my action, named `SetSlugAndPublishAction`:

// setSlugAndPublishAction.js

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

const sanityClientConfig = {
  projectId: process.env.SANITY_STUDIO_API_PROJECT_ID,
  dataset: process.env.SANITY_STUDIO_API_DATASET,
  token: process.env.SANITY_STUDIO_API_TOKEN,
  useCdn: true,
}

export default function SetSlugAndPublishAction(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: async () => {
      // This will update the button text 
      setIsPublishing(true)

      const client = sanityClient(sanityClientConfig)
      let slug = '', name = props.draft.name
      
      /// TODO: load a slugify function from the document for this task
      switch (props.type) {
        case 'tag':
          let gameName = props.draft.game._ref
          const query = '*[_type == "game" && _id == $gameID][0] {name}'
          const params = {gameID: gameName}

          await client.fetch(query, params).then(game => {
            gameName = !!game ? game.name : gameName
          })

          name = slug = `${gameName}-tag-${props.draft.tagnumber}`
          break;
        default:
          break;
      }

      // Set the slug
      patch.execute([{set: { slug: { _type: 'slug', current: slug.toLowerCase() }, name: name.toLowerCase() }}])
      
      // Perform the publish
      publish.execute()
      
      // Signal that the action is completed
      props.onComplete() 
    }
  }
}
Nice! Thanks for the example
user Q
you should add that as a sanity snippet on the community pages. Someone shared my pseudo gist code the other day too.
user L
this was definitely a collaboration between the both of us. I will look into creating my first snippet and give you credit where I can.
Do you know if there is a way to display a custom error message from a custom action? I want to do an API call before publish and if it fails I want to display some sort an error message to the user
user E
I am doing exactly that with a toast.
Actually wrong one. This is the toast info https://www.sanity.io/ui/docs/component/toast
Ooo nice! Thanks
import {useToast} from '@sanity/ui'
...

const toast = useToast()
...

toast.push({status:'error', title: error})

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?