Limit document creation by finite category list in Sanity

3 replies
Last updated: Feb 13, 2023
Hey.
I would like list of finite items (i.e. a category). Let’s say I got 5 items in the list. Every document that uses one item (category) in the list must remove that option for the next documents that will be created. This also implies that it will only be possible to have 5 documents of this particular type “live” at any time, as there will be no more catagories to chooose from.

Could someone help me understand which approach to choose here, or point me to some documentation that covers this?

Thanks in advance for all input.
AI Update

What you're describing is an interesting constraint system where you want to ensure only 5 documents can exist at any time because there are only 5 category options available, and each document must use a unique category.

Here are a few approaches you could take:

You can create a custom validation rule that queries existing documents to check if a category is already in use:

{
  name: 'category',
  type: 'reference',
  to: [{type: 'category'}],
  validation: Rule => Rule.custom(async (value, context) => {
    if (!value?._ref) return true;
    
    const {getClient} = context;
    const client = getClient({apiVersion: '2023-01-01'});
    
    // Check if this category is already used by another document
    const existingDocs = await client.fetch(
      `*[_type == $docType && category._ref == $categoryRef && _id != $currentId]`,
      {
        docType: context.document._type,
        categoryRef: value._ref,
        currentId: context.document._id
      }
    );
    
    if (existingDocs.length > 0) {
      return 'This category is already in use';
    }
    
    return true;
  }).required()
}

2. Dynamic Reference Filter

You can filter out already-used categories from the reference field options using the filter option on reference fields:

{
  name: 'category',
  type: 'reference',
  to: [{type: 'category'}],
  options: {
    filter: async ({document, getClient}) => {
      const client = getClient({apiVersion: '2023-01-01'});
      
      // Get all category IDs that are already used
      const usedCategories = await client.fetch(
        `*[_type == $docType && _id != $currentId].category._ref`,
        {
          docType: document._type,
          currentId: document._id
        }
      );
      
      return {
        filter: '!(_id in $usedCategories)',
        params: {usedCategories}
      };
    }
  }
}

3. Document-Level Validation

You could also add validation at the document level to prevent publishing when all categories are taken:

{
  name: 'myDocType',
  type: 'document',
  fields: [
    // your fields including category
  ],
  validation: Rule => Rule.custom(async (doc, context) => {
    const {getClient} = context;
    const client = getClient({apiVersion: '2023-01-01'});
    
    const totalCategories = await client.fetch(`count(*[_type == "category"])`);
    const usedCategories = await client.fetch(
      `count(*[_type == $docType && _id != $currentId])`,
      {
        docType: doc._type,
        currentId: doc._id
      }
    );
    
    if (usedCategories >= totalCategories && !doc._id) {
      return 'Cannot create new document: all categories are already in use';
    }
    
    return true;
  })
}

Important Notes

  • Client-side only: Remember that Sanity validation runs only in Studio, not on API mutations. If you're creating documents via API, you'll need additional server-side validation.
  • Performance: These queries run on every validation, so they may impact Studio performance slightly.
  • Race conditions: If two editors create documents simultaneously, there's a small window where both might select the same category before validation catches it.

The combination of approach #2 (filtering available options) and #1 (validation) gives the best user experience - users only see available categories, and validation provides a safety net.

Show original thread
3 replies
Check using a filter on the reference field, you can filter out all categories that are referenced already
Cool.
If I may ask:
I want these categories to be predefined. So, I don’’t want the user/admin to be able to create new ones. I tried to make an object with just a list of predefined strings and then use this object as a reference, but that didn’t work.

The categories are just names of colors. So “Blue”, “Red” and so on. Then, another document with the name “team” can use one of these colors, and they must all use a different one.

How would you set this up?
Can probably create it as a semi singleton https://www.sanity.io/guides/singleton-document

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?