
Grab your gear: The official Sanity swag store
Read Grab your gear: The official Sanity swag storeI can help you set up hierarchical slugs for parent/child page relationships! This is a common pattern for building nested navigation structures.
The issue you're experiencing (where slots/best-slots becomes just best-slots) is because the slug field automatically sanitizes input by removing forward slashes and other special characters. To create hierarchical URLs, you need to generate the full path dynamically by combining a parent reference with the page's own slug.
Here's the recommended approach:
First, add a reference field to your page document type that points to other pages:
defineField({
name: "parent",
type: "reference",
to: [{ type: "page" }],
group: "content",
})Keep your slug field generating from just the page title (not the full path):
defineField({
name: "slug",
type: "slug",
options: { source: "title" },
group: "content",
})So "Best Slots" would just have slug best-slots, not slots/best-slots.
When querying your pages, construct the full path by recursively fetching parent slugs. Here's a GROQ query that does this:
*[_type == "page"] {
_id,
title,
"slug": slug.current,
"fullPath": select(
defined(parent) => "/" + *[_id == ^.parent._ref][0].slug.current + "/" + slug.current,
"/" + slug.current
)
}For more complex nested hierarchies (grandparent > parent > child), you'll need to handle this in your frontend code by recursively building the path, or use a more advanced approach with custom slugify functions.
If you want the full path visible in Studio, you can create a custom slugify function that fetches the parent's slug using async operations. According to the slug field documentation, you can access the document context and use the Sanity client:
defineField({
name: "slug",
type: "slug",
options: {
source: "title",
slugify: async (input, schemaType, context) => {
const { parent } = context;
const client = context.getClient({ apiVersion: '2024-01-01' });
if (parent?._ref) {
const parentDoc = await client.fetch(
`*[_id == $parentId][0].slug.current`,
{ parentId: parent._ref }
);
if (parentDoc) {
return `${parentDoc}/${input.toLowerCase().replace(/\s+/g, '-')}`;
}
}
return input.toLowerCase().replace(/\s+/g, '-');
}
},
group: "content",
})Note that with this approach, if you change a parent's slug, child slugs won't automatically update - you'd need to handle that separately.
For most use cases, I'd recommend option #3 (building paths in the frontend) because:
You can also check out this recipe on setting slugs with referenced field values and this answer about using reference fields to generate slugs for more advanced patterns.
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.
Content operations
Content backend


The only platform powering content operations
By Industry


Tecovas strengthens their customer connections
Build and Share

Grab your gear: The official Sanity swag store
Read Grab your gear: The official Sanity swag store