Sanity Pioneers: Get early access to betas, extra AI credits, and a direct line to the engineering team. Apply now

Validating unique slugs with parent pages in Sanity hierarchical structure

11 repliesLast 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:

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

Was this answer helpful?

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.

Related contributions