Is there a way to resolve a reference in a custom validation?

10 replies
Last updated: Oct 14, 2020
Is there a way to resolve a reference in a custom validation?I have an array that can hold two different types of references. I would like to validate that it holds at least on of each type.

Thank you in advanced
πŸ™

{
  name: 'tags',
  title: 'Tags',
  type: 'array',
  of: [
    {
      type: 'reference',
      to: [{ type: 'artist' }, { type: 'category' }],
    },
  ],
  validation: (Rule) =>
    Rule.required().custom((data) => {
      /*
        That is what i get from the data item. 
        But I need to know the type of the reference (artist or category)
        
        data [{…}]
          0:
          _key: "b29ee1dbb22a"
          _ref: "96e38ca0-b075-4777-b8f4-d2d6ca123df0"
          _type: "reference"
      */

    }),
},
AI Update

Yes, you can resolve references in custom validation, but you'll need to fetch the referenced documents using the Sanity client. The reference data you receive in validation only contains _ref, _type, and _key - it doesn't include the actual document type (like artist or category) that you need.

Here's how to validate that your array contains at least one of each reference type:

import {defineField} from 'sanity'
import {getCliClient} from 'sanity/cli'

// Get a client instance
const client = getCliClient({apiVersion: '2024-01-01'})

export default defineField({
  name: 'tags',
  title: 'Tags',
  type: 'array',
  of: [
    {
      type: 'reference',
      to: [{type: 'artist'}, {type: 'category'}],
    },
  ],
  validation: (Rule) =>
    Rule.required().custom(async (tags) => {
      if (!tags || tags.length === 0) {
        return true // Let the required() rule handle this
      }

      // Fetch all referenced documents to get their types
      const ids = tags.map(tag => tag._ref)
      const documents = await client.fetch(
        `*[_id in $ids]{_id, _type}`,
        {ids}
      )

      // Check if we have at least one artist and one category
      const hasArtist = documents.some(doc => doc._type === 'artist')
      const hasCategory = documents.some(doc => doc._type === 'category')

      if (!hasArtist) {
        return 'You must include at least one artist'
      }
      if (!hasCategory) {
        return 'You must include at least one category'
      }

      return true
    }),
})

Important notes:

  1. Client setup: You need to import and configure the Sanity client. In Studio v3+, use getCliClient from sanity/cli for validation contexts.

  2. Performance: This validation runs a query on every change, so it adds some overhead. For better performance, you might want to debounce or only run this on certain conditions.

  3. Alternative approach using validation context: If you're using Studio v3, you can also access the client from the validation context:

validation: (Rule) =>
  Rule.custom(async (tags, context) => {
    const client = context.getClient({apiVersion: '2024-01-01'})
    
    if (!tags || tags.length === 0) return true
    
    const ids = tags.map(tag => tag._ref)
    const documents = await client.fetch(
      `*[_id in $ids]{_id, _type}`,
      {ids}
    )
    
    const hasArtist = documents.some(doc => doc._type === 'artist')
    const hasCategory = documents.some(doc => doc._type === 'category')
    
    if (!hasArtist) return 'You must include at least one artist'
    if (!hasCategory) return 'You must include at least one category'
    
    return true
  })

This approach is confirmed in this community answer where the same pattern is used to fetch and validate referenced documents. The key insight is that validation only receives the reference structure (_ref, _type, _key), not the actual document data, so you must fetch it explicitly to access document properties like the _type field of the referenced documents.

Sorry this was lost in the flow here... here's a suggestion:

import { uniq } from 'lodash'

...

      validation: Rule => Rule.custom(data => {
        const types = data.map(item => item._type)
        const uniqTypes = uniq(types)
        return uniqTypes.length > 1
          ? true
          : 'Must have at least one of each'
      }),
ah.. right you need the reference...
Yes! Thanks for getting back. I am referring to two different document types
[{ type: 'artist' }, { type: 'category' }]
. I want to validate that at least one of each category is in the array. But in the validation, i can not resolve toe reference. Is this even possible?
Something along these lines... (untested)

import { uniq } from 'lodash'
import client from 'part:@sanity/base/client'

...

      validation: Rule => Rule.custom(data => {
        const referencedIds = data.map(item => item._ref)
        return client.fetch(`*[_id in $ids]._type`, {
          ids: referencedIds
        })
        .then(res => {
          const uniqTypes = uniq(res)
          return uniqTypes.length > 1
            ? true
            : 'Must have at least one of each'  
        })
      }),
Nice! Does the client work out of the box, or do i need to add the credential somewhere?
It's preconfigured
Thanks a lot!
You might have to tweak it, it's just from the top of my head πŸ˜„
Sure, but knowing that i can use the client in the schema definitions opens a whole new universe for me 😎
joined the slack channel to find a solution to this same problem, very happy to have found it

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.

Was this answer helpful?