Getting an Array of References without an Object Wrapper
I can help you with this! The issue you're running into is about how GROQ handles projections at different levels in your query.
When you write:
*[_id == $id].featuredPosts[]->{The elements I want}This returns null because you're trying to project on an array that's been "unwrapped" from the document context. The featuredPosts[] syntax expands the array, but then the ->{} projection has no document context to work with.
The solution is to use a projection directly on the dereferenced array without wrapping it at the document level. Here's the correct syntax:
*[_id == $id][0].featuredPosts[]->{
title,
slug,
// other fields you want
}The key differences:
- Use
[0]to get the single document (since you're filtering by_id) - The projection
{}comes after the dereference operator-> - This returns the array directly without an object wrapper
If you need to work with multiple documents and want just the array, you can use the pipe operator:
*[_id == $id][0].featuredPosts[] -> {
title,
slug
}The reason wrapping featuredPosts in curly brackets works ({featuredPosts: [...]}) is because that creates a projection at the document level, which maintains the object structure. But since you want just the array, accessing it directly from the document and then dereferencing gives you the unwrapped array result.
Pro tip: If you're doing this frequently, you might also consider using the spread operator in a projection:
*[_id == $id][0]{
"posts": featuredPosts[]->{title, slug, _id}
}.postsThen access .posts to get just the array, though the first approach is cleaner for this use case.
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.