Set slug on publish with referenced field value

By Ken Eucker

Set slug on publish document action

sanity.json

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

documentActions.js

// schemas/documentActions.js

import defaultResolve, {
    PublishAction,
  } from 'part:@sanity/base/document-actions';
  import SetSlugAndPublishAction from "./actions/setSlugAndPublishAction.js"
  
  /// Publish Actions
  export default function useDocumentActions(props) {
    /// Leave the code below commented out to run this action for all documents
    /// Uncomment the code below to restrict this action to specific documents only
    // if (["tag"].indexOf(props.type) !== -1) {
      return defaultResolve(props).map((Action) =>
        Action === PublishAction ? SetSlugAndPublishAction : Action
      );
    // }

    // return defaultResolve(props)
  }

setSlugAndPublishAction.js

// schemas/actions/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)

      /// Get the sanity client for fetching referenced field values
      const client = sanityClient(sanityClientConfig)

      /// Set the initial slug value to the name field
      let slug = props.draft.name
      
      /// Set the slug based on the document type
      switch (props.type) {
        case 'tag':
          /// The type of the reference field to get the value from
          const referenceNameType = 'category'
          /// Query for the referenced "category" and return the "name" field of that referenced document
          const query = `*[_type == "${referenceNameType}" && _id == $nameRef][0] {name}`
          /// Start with the "category: reference id in the draft
          let referenceName = props.draft.category._ref
          /// Fetch the category referenced in this document
          await client.fetch(query, {nameRef: referenceName}).then(category => {
            /// Set the referenceName to the category.name field
            referenceName = !!category ? category.name : referenceName
          })

          /// For the "tag" type document, set the slug to [category.name]-[type]-[number]
          slug = `${referenceName}-${props.type}-${props.draft.number}`
          /// Set name field for the "tag" type document to be the same value as the slug
          patch.execute([{set: { name: slug.toLowerCase() }}])
          break;

        default:
          /// Doing nothing? Consider deleting this switch statement to simplify your code.
          break;
      }

      /// Set the slug field of this document
      patch.execute([{set: { slug: { _type: 'slug', current: slug.toLowerCase() }}}])
      
      // Perform the publish
      publish.execute()
      
      // Signal that the action is completed
      props.onComplete() 
    }
  }
}

What this code does, in order of operations, is the following:

  1. Loads the documentActions.js file from within the sanity.json configuration file. See sanity.json.
  2. Sets the publish action for the configured documents (all, by default) to the new SetSlugAndPublishAction method. See documentActions.js.
  3. Upon pressing the publish button in sanity studio, the SetSlugAndPublishAction method will query your configured sanity dataset for the coded reference field and set the name of that referenced document in the slug field for the publishing document.

This code makes the following assumptions about your code, which you can modify to meet your schema's needs, using simple field names for the sake of education:

  • A document named "tag" is present in your schema.
  • The "tag" document has these three fields: number, category, and slug.
  • The "category" field within the "tag" document is a reference to a "category" document.
  • A document named "category" is present in your schema.
  • The "category" document has a name field within it, of type string, and presumably a slug field.
  • All documents defined in the schema have a slug and name field.

My use case for this code was to meet the following needs:

  • To generate a slug for a given document that was made up of values within that document, upon publishing the document.
  • To include in the slug a "category-like" name and a number -- derived from fields within that document.
  • For the "category-like" name to come from a field of type reference within the document.
  • For the slug to contain the more readable "name" of the "category-like" field within the document, not the "_ref" value.

Your mileage may vary.

This code example could have been simplified by setting the slug in the same way for all document types. In conjunction with that simplification, the commented-out code in the documentActions.js file could be an easier way to separate the logic between different document types and the way that the slug field is set upon publish, or to only include this publish action with a single document type.

Contributor