Validating unique slugs with parent pages in Sanity hierarchical structure

11 replies
Last updated: Nov 29, 2025
Hi everyone, I have a client website with alot of pages, most have parent pages which will alter the slug so technically they are all different but the page slug itself may be duplicated across pages e.g. a page will have a slug of 'north-east' but they will all have different parents. I've tried to change the validation to return true if the there is only 1 occurence of the slug with a parent but it still returns the error of 'Slug is already in use' Any ideas how I can get around this?
Here is my current slug, parentPage code:


{
			name: 'slug',
			title: 'Slug',
			type: 'slug',
			options: {
				source: 'name',
				slugify: (input) =>
					input
						.toLowerCase()
						//Remove spaces
						.replace(/\s+/g, '-')
						//Remove special characters
						.replace(/[&\/\\#,+()$~%.''":*?<>{}]/g, ''),
			},
			validation: Rule => Rule.custom((slug, section) => {
				console.log(slug, section.parent.parentPage._ref)
				let parentRef = '*[_type=="pillar" && slug.current == $slug]{parentPage{_ref}}'
				const params = {slug: slug.current}
				return sanityClient.fetch(parentRef, params).then(matchingParents => {
					let count = 0;
					console.log({matchingParents})
					matchingParents.forEach(page => {
						page.parentPage._ref === section.parent.parentPage._ref ? count++ : count
					})

					console.log({count})
					if(count === 1) {
						return true
					} else {
						return false
					} 
				})
			})
		},

		{
			name: 'parentPage',
			title: 'Parent Page',
			type: 'reference',
			to: [{ type: 'pillar' }],
			// This ensures we cannot select other "children"
			options: {
				filter: '!defined(parentPage)',
			},
		},
AI Update

Looking at your code, I can see the issue. You're trying to use custom validation on the slug field, but Sanity has a specific isUnique option for slug fields that should be used instead. The built-in uniqueness check is still running and causing the error.

Here's how to fix it:

Create a separate isUnique function:

// lib/isUniqueSlugWithParent.js
export async function isUniqueSlugWithParent(slug, context) {
  const { document, getClient } = context;
  const client = getClient({ apiVersion: '2025-02-19' });
  
  const id = document?._id;
  const parentRef = document?.parentPage?._ref;
  
  // If no slug or no ID, consider it valid
  if (!slug?.current || !id) {
    return true;
  }

  // Remove draft prefix if present
  const publishedId = id.replace(/^drafts\./, '');

  // Check if any OTHER document has the same slug + parent combination
  const query = `count(*[
    _type == "pillar" && 
    slug.current == $slug && 
    parentPage._ref == $parentRef &&
    !(_id in [$id, $draftId])
  ])`;

  const params = {
    slug: slug.current,
    parentRef: parentRef,
    id: publishedId,
    draftId: `drafts.${publishedId}`
  };

  const count = await client.fetch(query, params);
  return count === 0; // Return true if unique (count is 0)
}

Update your schema to use the isUnique option:

import { isUniqueSlugWithParent } from '../lib/isUniqueSlugWithParent';

{
  name: 'slug',
  title: 'Slug',
  type: 'slug',
  options: {
    source: 'name',
    slugify: (input) =>
      input
        .toLowerCase()
        .replace(/\s+/g, '-')
        .replace(/[&\/\\#,+()$~%.''":*?<>{}]/g, ''),
    isUnique: isUniqueSlugWithParent  // Use this instead of validation
  }
  // Remove the validation property entirely
},

{
  name: 'parentPage',
  title: 'Parent Page',
  type: 'reference',
  to: [{ type: 'pillar' }],
  options: {
    filter: '!defined(parentPage)',
  },
}

Key differences from your code:

  1. Use isUnique option instead of validation - The slug field has a special isUnique callback that overrides the default uniqueness check
  2. The query checks the right thing - It looks for documents with the same slug AND same parent, excluding the current document (both draft and published versions)
  3. Return boolean directly - isUnique expects true (unique) or false (not unique), unlike validation functions that can return error messages
  4. Handle drafts properly - The query excludes both the published and draft versions of the current document

This will allow you to have multiple pages with slug "north-east" as long as they have different parent pages. The uniqueness is now scoped to the combination of slug + parent page.

Show original thread
11 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.

Was this answer helpful?