How to validate that a document is referenced by at least one other document?

25 replies
Last updated: Mar 9, 2022
Hi there! I have a data model with two document types, foo and bar, and bar’s can contain references to multiple foos. We want to warn if any foo exists that isn’t referenced by at least one bar, but I’m not sure how to navigate references in validation. Is there a way in the validation of foo to say “referenced by at least one bar”?
AI Update

I understand you want to validate that a foo document is referenced by at least one bar document. This is definitely possible using custom validation in Sanity, though it requires querying within your validation rule.

In your foo document schema, you can add a custom validation rule that queries the Content Lake to check if any bar documents reference the current foo. Here's how:

import { defineType } from 'sanity'

export default defineType({
  name: 'foo',
  type: 'document',
  fields: [
    // your fields here
  ],
  validation: Rule => Rule.custom(async (doc, context) => {
    const { document, getClient } = context
    const client = getClient({ apiVersion: '2024-01-01' })
    
    // Query to find any bar documents that reference this foo
    const query = `count(*[_type == "bar" && references($fooId)])`
    const count = await client.fetch(query, { fooId: document._id })
    
    if (count === 0) {
      return 'Warning: This foo is not referenced by any bar documents'
    }
    
    return true
  })
})

A few important notes:

The references() function in GROQ is perfect for this - it checks if a document contains a reference to the specified ID anywhere in its structure, which works great for your use case where bar contains references to multiple foos.

Validation context provides getClient() which gives you access to query the Content Lake. You'll need to specify an API version when getting the client.

Warning vs Error: The validation above returns a string message which shows as a validation error and blocks publishing. If you want it to be a warning instead (non-blocking), you can structure it differently:

validation: Rule => Rule.warning().custom(async (doc, context) => {
  const { document, getClient } = context
  const client = getClient({ apiVersion: '2024-01-01' })
  
  const query = `count(*[_type == "bar" && references($fooId)])`
  const count = await client.fetch(query, { fooId: document._id })
  
  if (count === 0) {
    return 'Warning: This foo is not referenced by any bar documents'
  }
  
  return true
})

Performance consideration: This validation runs a query every time the document is validated, which could impact Studio performance if you have many documents. For better performance, you might want to consider only running this check on certain events rather than on every keystroke.

The Sanity validation documentation has more details on validation rules, particularly the section on asynchronous validation using the client.

Alternatively, you could approach this from the other direction - add a custom component or dashboard widget on bar documents that shows which foo documents are orphaned, making it more of a content management workflow tool rather than a validation rule on each individual foo.

Show original thread
25 replies
It is possible! In this case, I'd probably use document-level validation in conjunction with the client.
  validation: Rule => Rule.custom(async ({ _id }) => {
    //count the incoming references from Foo docs
    const referenceCount = await studioClient.fetch(`count(*[_type == 'foo' && references(${_id})])`)
    //Return error if 0 Foo docs reference Bar
    return referenceCount > 0 ? true : 'Document must have at least 1 Foo referencing it'
  }),
Ah makes total sense! I’ll give it a whirl. Thank you!
user M
Maybe a dumb question but if I don't ask, I don't know -- what happens with a false with document validation? You can't commit the change? Do we control the response, like if there's warning text? I couldn't tell from reading that.
In my case, I’ll be adding .warning() and putting up warning text
By default, it would fail to publish I believe
By default it will give you an alert
!
that the field isn’t valid, but you can also add a custom warning and nest a string or other error prop return as needed into that.
here’s a feedback example when a string doesn’t match the default url type pattern
Thank you both! Sounds like it extends the field-level dynamics and is just another way to call them up.
Okay a more generic question now that I’ve tried and failed to use Racheal’s snippit — any pro tips for debugging validation?
Yep! All of that is correct! There are different error levels that you can control. The validation I provided would prevent you from publishing a document (which may be a bad experience for your editors). As Matthew said, if you append
.warning()
you would still be able to publish, but the document would indicate an issue.
user B
you likely need to bring in the client. Mine uses one that I preconfigure and reuse through my studio. Notice the
studioClient.fetch()
. Mine looks like this:
import client from "part:@sanity/base/client"

export const studioClient = client.withConfig({apiVersion: '2021-03-25'})
Then I just add
import studioClient from './path/to/studioClient.js'
to the top of my document
Makes total sense, but is there somewhere that I would have been able to debug that myself? Surprised I don’t see an error message anywhere in the studio about my validation query that definitely won’t work
I would think in your console it would tell you that studioClient is undefined.
Nothing in the console 😕
Ah, it tells you in the error, actually.
ahhhhh there it is
I’ve got that
My brain just filtered it out
Okay awesome, I can take it from here. Thanks for teaching me how to fish 😉
Happy to help!
Phew, finally got it working! From your snippit I needed to change
references(${id})
to
references('${id}')
or I got a super unhelpful
expected ')' following function arguments
error. One follow up now that it’s working: I don’t see anything in the docs around the async + await syntax you used. Is that a sanity thing, or just generic javascript and I’m just a js newb?
It's a general JS thing. There's a good explanation of the async and await keywords here .
It’s a typical js API rendering method Link and one of the more obscure and confounding things to learn, so don’t be embarrassed if it doesn’t come quickly or easily.
Thanks all!
Really great question
user B
- thank you!

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?