How to Neatly Organize GROQ Queries
I totally feel your pain with the nested select() statements – they can get unwieldy fast! While GROQ doesn't have a built-in "switch" statement or pattern matching, there are a few approaches that can make your query more maintainable.
Use GROQ Parameters to Simplify Logic
The best approach is to move some of the conditional logic to your application code and use GROQ parameters to pass in pre-computed values. This keeps your GROQ query cleaner and easier to test.
Build the Query String Conditionally
A cleaner approach is to construct different query strings based on your parameters in JavaScript:
function buildInsightsQuery({ order, insightType, filter }) {
const orderDir = order === "asc" ? "asc" : "desc";
let filterClause = `_type == $insightType`;
if (filter === "past") {
filterClause += ` && now() > date`;
} else if (filter === "future") {
filterClause += ` && now() < date`;
}
return `*[${filterClause}] | order(date ${orderDir})`;
}
// Usage
const query = buildInsightsQuery({ order: "asc", insightType: "insight", filter: "past" });
const result = await client.fetch(query, { insightType });Flatten with Boolean Logic
If you want to keep it in GROQ, you can sometimes flatten nested selects using boolean operators:
"insights": *[
_type == ^.insightType
&& (
!defined(^.filter)
|| (^.filter == "past" && now() > date)
|| (^.filter == "future" && now() < date)
)
] | order(date select(^.order == "asc" => asc, desc))This uses the || (OR) operator to handle multiple conditions at once, which can be easier to read than deeply nested selects. The order() function can still use select() for the direction.
Projection with Conditional Logic
Another pattern is to create a projection that adds computed fields, then filter on those:
*[_type == ^.insightType] {
...,
"matchesFilter": select(
!defined(^.filter) => true,
^.filter == "past" => now() > date,
^.filter == "future" => now() < date,
false
)
}[matchesFilter] | order(date select(^.order == "asc" => asc, desc))This separates the filtering logic from the query structure, making it a bit more modular.
My Recommendation
For complex conditional queries like yours, I'd recommend building the query string in JavaScript. It gives you:
- Better readability and maintainability
- Easier testing (you can unit test your query builder)
- More flexibility as your options grow
- The ability to use proper conditionals instead of nested selects
The GROQ query language is powerful, but it's not designed to be a full programming language. When the logic gets complex, leveraging your application code is usually the right call. You can still use parameters for the actual values to keep things safe and maintainable, as explained in the GROQ Parameters documentation.
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.