Limit document creation by finite category list in Sanity
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:
1. Custom Validation with GROQ Query (Recommended)
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 thread3 replies
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.