Saskia Bobinska
Senior Support Engineer @Sanity
How to validate nested fields from the document or object level
import {defineField, defineType} from 'sanity'
export const ObjectValdationType = defineType({
name: 'objectValidation',
title: 'Object Validation',
type: 'object',
validation: (Rule: Rule) =>
Rule.custom((context: ValidationContext) => {
if (context.type === 'inline') {
return context.form?.label
? true
: {
message: 'Button Label cannot be empty in an inline form',
paths: ['form', ['form', 'label']],
}
}
return true
}),
fields: [
defineField({
name: 'type',
title: 'Type',
type: 'string',
options: {
list: [
{title: 'Inline', value: 'inline'},
{title: 'Referenced', value: 'referenced'},
],
},
}),
defineField({
name: 'formReference',
title: 'Form Reference',
type: 'reference',
to: [{type: 'form'}],
hidden: ({parent}) => parent?.type !== 'referenced',
}),
defineField({
name: 'form',
title: 'Form',
type: 'object',
fields: [
defineField({
name: 'label',
title: 'buttonLabel',
type: 'string',
}),
],
}),
],
})
...
defineField({
name: 'embededForm',
title: 'Form',
type: 'objectValidation',
validation: (Rule: Rule) =>
Rule.custom((context: ValidationContext) => {
if (context.type === 'inline') {
return context.form?.label
? true
: {
message: 'Button label cannot be empty in inline forms',
paths: ['form', ['form', 'label']],
}
}
return true
}),
}),
...
When validating objects and nested objects it can be hard to setup more complex validation structures.
Let's say you have an object for Forms you use in Portable Text. In this object you can either embed a reusable form (reference to a form) or you can create a single use inline form. In those single use forms, you always have the label for the button so now you need to add nested validation.
Why? Because setting the label as required will mean, you will need to fill it our regardless if you have a referenced or inline form.
If you hide the fields not used like in the example, your editor will not be able to select or fill out the label because:hidden: ({parent}) => parent?.type !== 'referenced',
An option I personally like is then adding a top level validation, that can use the paths for nested fields to validate for different conditions among its children.
So in our example you see that we want to display the error messages on the form object level 'form'
, as well as the label field level ['form', 'label']
, so the paths together will be: paths: ['form', ['form', 'label']]
This means you could add as many nested validation steps, without calling the document through the validation context. This will also work for the instances, where you only want to validate an object in a certain spot: Instead of adding the validation to the object schema definition, you can then add the validation to the instance itself like detailed in the second example (Object field validation only in certain instances)
Senior Support Engineer @Sanity
If you install plugins or other packages with conflicting (peer) dependencies, you can use this useful workaround to unblock yourself.
Go to What to do when Studio crashes due to conflicts in dependency versions?When creating custom document actions, it can be necessary to check, wether all validation rules are full-filled.
Go to Use the validation status of a document in your custom document actionsOnly return a value when a condition is fulfilled
Go to Conditional values in GROQ queriesIf you want to make it possible to use parameterised initialValue templates in reference fields, this is how!
Go to Create a new reference document with parameterised initial Values