Auto-generate readOnly/hidden field value from another field in Sanity
sourceand
slufigyoptions but it doesn't get created automatically set to a value upon publish.
The slug field's source and slugify options don't automatically generate the slug value on publish - they only power the "Generate" button in the Studio UI. However, you can automatically set a slug (or any readOnly/hidden field) on publish using a custom document action.
Here's the correct way to implement this in Studio v3:
Create a Custom Publish Action
First, create your custom action component (e.g., actions/SetSlugAndPublish.tsx):
import { useDocumentOperation } from 'sanity'
import { useState, useEffect } from 'react'
import type { DocumentActionComponent } from 'sanity'
function generateSlug(text: string | number): string {
return String(text)
.toLowerCase()
.replace(/\s+/g, '-')
.replace(/[^\w-]+/g, '')
}
export const SetSlugAndPublishAction: DocumentActionComponent = (props) => {
const { patch, publish } = useDocumentOperation(props.id, props.type)
const [isPublishing, setIsPublishing] = useState(false)
useEffect(() => {
// Reset publishing state if publish completes
if (isPublishing && !publish.disabled) {
setIsPublishing(false)
}
}, [isPublishing, publish.disabled])
return {
label: isPublishing ? 'Publishing...' : 'Publish',
disabled: publish.disabled || isPublishing,
onHandle: () => {
setIsPublishing(true)
// Generate slug from your source field(s)
// For a number field:
const slug = generateSlug(props.draft?.yourNumberField || '')
// Patch the document with the slug
patch.execute([{ set: { slug: { _type: 'slug', current: slug } } }])
// Then publish
publish.execute()
props.onComplete()
}
}
}Register the Action in Your Config
In your sanity.config.ts (or .js):
import { defineConfig } from 'sanity'
import { SetSlugAndPublishAction } from './actions/SetSlugAndPublish'
export default defineConfig({
// ... other config
document: {
actions: (prev, context) => {
// Replace the default publish action for specific document types
if (context.schemaType === 'yourDocumentType') {
return prev.map((originalAction) =>
originalAction.action === 'publish'
? SetSlugAndPublishAction
: originalAction
)
}
return prev
}
}
})Key Points for Studio v3
Important differences from older patterns:
Import from
sanitypackage: Useimport { useDocumentOperation } from 'sanity'- NOT from@sanity/react-hooks(deprecated)Return an action description object: Your component must return an object with
label,onHandle, and optionallydisabled,icon,shortcut, etc. - NOT a React componentUse the callback pattern: In
defineConfig, usedocument.actions: (prev, context) => [...]to access context and modify the actions arrayFor multiple source fields: You can combine fields like this:
const slug = generateSlug(`${props.draft?.field1} ${props.draft?.field2}`)For number fields specifically: Just convert to string:
const slug = generateSlug(String(props.draft?.yourNumberField))
Alternative: Using Initial Value Templates
If you want the slug set when the document is created (not on publish), you can use Initial Value Templates instead:
import { defineConfig } from 'sanity'
export default defineConfig({
// ... other config
schema: {
templates: (prev) => [
...prev,
{
id: 'document-with-slug',
title: 'Document with Slug',
schemaType: 'yourDocumentType',
value: (params) => ({
slug: {
_type: 'slug',
current: generateSlug(params.yourNumberField || 'default')
}
})
}
]
}
})This approach works great for hidden or readOnly fields that should be set automatically based on other field values when publishing or creating documents.
Show original thread12 replies
Was this answer helpful?
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.