Form hooks provide access to document values, form state, and field metadata within Sanity Studio. Use these hooks in custom input components and form-related UI.
Retrieve the value of any field in the current document by path.
function useFormValue(path: Path): unknown
Array notation with segments that are either:
- Strings representing field names
- Numbers for array indices (simple values)
- Objects with
_key for array items (objects with keys)
Returns: The value at the specified path, or undefined if not found.
Example:
import {useFormValue, type StringInputProps} from 'sanity'
function ConditionalInput(props: StringInputProps) {
// Get value of a sibling field
const category = useFormValue(['category'])
// Get value from nested object
const authorName = useFormValue(['author', 'name'])
// Get value from array by index
const firstTag = useFormValue(['tags', 0])
// Get value from array by key
const specificItem = useFormValue(['items', {_key: 'abc123'}])
if (category !== 'article') {
return null // Hide this input unless category is 'article'
}
return <input {...props.elementProps} />
}
Notes:
- Must be used within a form context (inside a document editor)
- Returns
undefined for non-existent paths
- Re-renders when the value at the path changes
Access the complete form state including field metadata, validation, and presence.
function useFormState(options: UseFormStateOptions): FormState | null
Root schema type for the document
Value to compare against (for change tracking)
Currently focused field path
Currently open field path (for dialogs/modals)
presence
FormNodePresence[]
required
Array of user presence data for collaborative editing
validation
ValidationMarker[]
required
Validation messages for all fields
Whether the form is read-only
Returns: ObjectFormNode containing:
value: Current document value
members: Array of field/fieldset members with their state
validation: Validation markers
presence: User presence data
readOnly: Computed read-only state
hidden: Computed hidden state
level: Nesting level (0 for root)
Example:
import {useFormState} from 'sanity'
function DocumentMetadata() {
const formState = useFormState({
schemaType,
documentValue,
comparisonValue: null,
focusPath: [],
openPath: [],
presence: [],
validation: [],
perspective: 'default',
hasUpstreamVersion: false,
})
if (!formState) return null
return (
<div>
<h3>Document Fields</h3>
{formState.members.map((member) => {
if (member.kind === 'field') {
return (
<div key={member.name}>
{member.name}: {member.field.validation.length} errors
</div>
)
}
return null
})}
</div>
)
}
useFieldActions
Access field-level actions and state (focus, hover, comments).
function useFieldActions(): FieldActionsState
Returns: Object containing:
Whether this field currently has focus
Whether this field is currently hovered
Rendered action buttons for this field
Handler to call when mouse enters field
Handler to call when mouse leaves field
Internal comment system state
Internal slot for additional UI
Example:
import {useFieldActions} from 'sanity'
function MyInput(props) {
const {
focused,
hovered,
actions,
onMouseEnter,
onMouseLeave
} = useFieldActions()
return (
<div
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
style={{
borderColor: focused ? 'blue' : hovered ? 'gray' : 'transparent'
}}
>
<input {...props.elementProps} />
{actions}
</div>
)
}
Access the document form instance with methods for patching, focusing, and managing document state.
function useDocumentForm(): DocumentFormContextValue
Returns: Object containing:
Whether the form is initialized and ready
documentValue
SanityDocument | undefined
Current document value
patch
(patches: FormPatch[]) => void
Apply patches to the document
Currently focused field path
Currently open path (for modals)
Programmatically focus a field
All validation markers for the document
Example:
import {useDocumentForm} from 'sanity'
function ValidationSummary() {
const {validation, setFocusPath} = useDocumentForm()
const errors = validation.filter(v => v.level === 'error')
return (
<div>
<h3>{errors.length} Errors</h3>
{errors.map((error, i) => (
<div key={i}>
<button onClick={() => setFocusPath(error.path)}>
Jump to error
</button>
{error.message}
</div>
))}
</div>
)
}
Get a function to retrieve form values without subscribing to changes (for use in callbacks).
function useGetFormValue(): (path: Path) => unknown
Returns: Function that takes a path and returns the current value at that path.
Example:
import {useGetFormValue} from 'sanity'
function MyInput(props) {
const getFormValue = useGetFormValue()
const handleClick = () => {
// Get current value without causing re-render
const currentCategory = getFormValue(['category'])
console.log('Category:', currentCategory)
}
return <button onClick={handleClick}>Log Category</button>
}
Difference from useFormValue:
useFormValue: Subscribes to changes, causes re-renders
useGetFormValue: Returns a getter function, no re-renders
Type Definitions
Path
Array representing a field path in the document:
type Path = Array<string | number | {_key: string}>
Examples:
// Top-level field
['title']
// Nested field
['author', 'name']
// Array index
['tags', 0]
// Array item by key
['blocks', {_key: 'abc123'}]
// Deeply nested
['metadata', 'seo', 'keywords', 2]
type FormState<T = Record<string, unknown>> = ObjectFormNode<T>
interface ObjectFormNode<T> {
kind: 'object'
value: T | undefined
schemaType: ObjectSchemaType
members: Array<FieldMember | FieldSetMember>
validation: ValidationMarker[]
presence: FormNodePresence[]
readOnly: boolean
hidden: boolean
level: number
path: Path
// ... additional internal properties
}
interface FormNodePresence {
user: {
id: string
displayName: string
imageUrl?: string
}
path: Path
sessionId: string
lastActiveAt: string
}
ValidationMarker
interface ValidationMarker {
level: 'error' | 'warning' | 'info'
message: string
path: Path
item?: {
message: string
paths?: Path[]
}
}
Best Practices
function DependentInput(props: StringInputProps) {
const parentType = useFormValue(['type'])
// Only render if parent field has specific value
if (parentType !== 'custom') return null
return <input {...props.elementProps} />
}
function MyInput(props) {
const getFormValue = useGetFormValue()
// Don't use useFormValue here - it would cause unnecessary re-renders
const handleSave = useCallback(() => {
const title = getFormValue(['title'])
// Use title in save logic
}, [getFormValue])
return <button onClick={handleSave}>Save</button>
}
Validate Against Other Fields
import {defineField} from 'sanity'
defineField({
name: 'endDate',
type: 'date',
validation: (Rule) => Rule.custom((endDate, context) => {
// Access sibling field using context.parent
const startDate = context.parent?.startDate
if (endDate && startDate && endDate < startDate) {
return 'End date must be after start date'
}
return true
})
})