Filtering out null/empty entries in GROQ subqueries for array fields in Sanity.io
Yes, this is definitely possible with GROQ! The key is understanding that you need to filter at the right level and use the correct syntax. Let me break down the approaches:
The Core Issue
Your attempted syntax 'hero': *[hero.heading != null] won't work because you can't use the *[filter] syntax inside a projection like that. The * operator is specifically for fetching documents from the dataset, not for filtering within projections.
Solution 1: Object-Level Conditionals (Recommended)
The cleanest approach is to use object-level conditional syntax with the format condition => { }. This only includes the object when the condition is true:
*[_type == "page" && slug.current == $slug] {
'content': content[] {
_type,
defined(hero->heading) => {
'hero': hero-> {
heading,
subheading
}
},
defined(image->file) => {
'image': image-> {
'src': file.asset->url,
caption
}
}
}
}The condition => { } syntax expands the object into the projection only when the condition evaluates to true. If false, nothing is added.
Solution 2: Using select() for Field-Level Logic
The select() function works like a switch statement and is great for field-level conditionals:
*[_type == "page" && slug.current == $slug] {
'content': content[] {
_type,
'hero': select(
defined(hero->heading) => hero-> {
heading,
subheading
}
),
'image': select(
defined(image->file) => image-> {
'src': file.asset->url,
caption
}
)
}
}With select(), if no condition matches and there's no fallback value, it returns null. The syntax is select(condition => value, condition2 => value2, fallback).
Solution 3: Filter the Array Before Projecting
If you want to completely remove array items that don't have the required content (rather than just omitting fields), filter at the array level:
*[_type == "page" && slug.current == $slug] {
'content': content[
_type == "page-block-hero" && defined(hero->heading) ||
_type == "page-block-image" && defined(image->file)
// add conditions for other block types
] {
_type,
'hero': hero-> {
heading,
subheading
},
'image': image-> {
'src': file.asset->url,
caption
}
}
}This filters the content array itself before projection, keeping only items that meet your criteria.
Key Functions
defined()- Returnstrueif a property exists and is notnullselect()- Evaluates conditions in order and returns the first matching value- Conditional projection (
condition => {}) - Expands objects into projections only when conditions are met
My recommendation: Use the object-level conditional approach (Solution 1) for your page builder use case. It's the most readable and maintainable, especially when different block types have different required fields.
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.