
Grab your gear: The official Sanity swag store
Read Grab your gear: The official Sanity swag storeYes, 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:
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.
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.
select() for Field-Level LogicThe 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).
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.
defined() - Returns true if a property exists and is not nullselect() - Evaluates conditions in order and returns the first matching valuecondition => {}) - Expands objects into projections only when conditions are metMy 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 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