👋 Next.js Conf 2024: Come build, party, run, and connect with us! See all events

Validation of children in objects

By Saskia Bobinska

How to validate nested fields from the document or object level

Object with Validation

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',
        }),
      ],
    }),
  ],
})

Object field validation only in certain instances

...
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)

Contributor

Other schemas by author