How to avoid nested parent queries and generate full slug path in Sanity?
Unfortunately, GROQ doesn't have built-in loops or recursive traversal capabilities, so you can't avoid the manual nesting you're currently doing if you need to traverse an unknown depth of parent references. However, there are some practical approaches to handle this:
The Reality: No Native Recursion
GROQ doesn't support recursive queries or loops. The reference access operator (->) resolves one level at a time, so you need to manually nest the parent-> chains as you're doing. If you need to support arbitrary depth, you'll need to handle this in your application code rather than in the query itself.
Better Approaches
1. Build the query programmatically (recommended for your case)
Based on this similar question from the Sanity community, the best approach is to build your GROQ query dynamically in JavaScript before executing it:
const slug = [null, 'parent', 'page']
const parentPageSelector = `parent->`;
const idQuery = slug
.map(
(_segment, index, slug) =>
`${parentPageSelector.repeat(index)}slug.current == $slug[${slug.length - 1 - index}]`
)
.join('&& \n');
const query = `*[${idQuery}]`;This generates the nested query structure you need based on your actual URL depth.
2. Build the full path in application code
Query with a reasonable nesting depth (4-5 levels), then recursively traverse the parent chain in JavaScript:
function buildFullSlug(page) {
const slugs = [];
let current = page;
while (current) {
if (current.slug) {
slugs.unshift(current.slug);
}
current = current.parent;
}
return slugs.join('/');
}3. Denormalize with a computed field
Consider adding a fullSlug field to your documents that gets computed when you save. You could use Sanity Functions with a document change event handler to automatically rebuild the full slug whenever a page or any of its ancestors changes. This is the most performant option for read-heavy applications since you compute once and query many times.
Joining Strings in GROQ
Regarding your question about joining slugs in the query itself - GROQ does have string manipulation functions available in the GROQ Functions reference, but they only work on arrays of strings at the same level. You can't use them to traverse nested parent relationships. For example, if you had a flat array of slugs, you could do:
string::join(arrayOfSlugs, "/")But since your slugs are in nested parent objects, you'd need to first collect them into an array, which GROQ can't do recursively.
Recommended Solution
For your use case, I'd recommend:
- Build your GROQ query programmatically using
string.repeat()as shown above - this handles variable depth cleanly - Or compute the
fullSlugas a field on your documents using Sanity Functions to avoid computing it on every request - best for performance - Keep your manual nesting to a reasonable depth (4-5 levels) as a fallback if you prefer pure GROQ
This gives you the best balance of query performance and flexibility without fighting against GROQ's limitations. The programmatic query building approach is actually quite elegant and handles the breadcrumb/full slug path problem nicely!
Show original thread5 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.