Reference

GROQ support matrix

What is supported (all of it, by evaluation), what is pushed into SQL (a growing subset), and where behaviour differs.

The honest summary: evaluation is complete, narrowing is partial, and only narrowing affects speed.

Language support

ConstructStatus
Filters, projections, nested projectionsSupported
Dereferencing with arrow syntax, including in projectionsSupported, batched per request
Ordering, slices, count(), defined(), coalesce()Supported
Conditionals and the spread operator in projectionsSupported
Functions: string, math, array, dateSupported by the evaluator
text::matchSupported; ranking is Postgres' rather than Sanity's
Parent scope (^), including inside a nested subquerySupported
A second *[...] nested inside a querySupported
score() and ordering by scorePartial
geo:: functionsNot supported

Pushed into SQL today

  • _type equality and membership
  • _id equality and membership
  • defined(field)
  • plain field equality, and slug.current equality (which has its own index)
  • the perspective's draft or published discrimination
  • order(...) and a trailing slice, when lifting them cannot change the result

Everything else is evaluated after the rows are fetched. A query that cannot be narrowed is slower; it is never wrong.

A note on nested queries

A query may contain more than one *[...], and the parent scope works inside them, so the natural parent-with-children shape is one query:

groq
*[_type == "docCategory"] | order(order asc){
  title,
  "articles": *[_type == "docArticle" && category._ref == ^._id] | order(order asc)
}

That is the query the documentation sidebar on this site is built from.

It is worth knowing what the planner does with it, because it is the one place where narrowing is visible in the shape of a query rather than only in its speed. Every *[...] in a query is evaluated against the same candidate set, so the predicate pushed into Postgres is the union of every filter's — here type in ('docCategory', 'docArticle') — and no row cap is pushed down, since a cap chosen for the outer query would withhold rows the inner one needs. The practical consequence: a nested query whose filter the planner cannot narrow widens the whole candidate set, so keep a _type on the inner filter.

Behavioural differences worth knowing

  • Sync tags widen for dereferencing queries. The documents reached through an arrow are not known before the query runs, so the read is tagged with the dataset rather than with those documents.
  • resultSourceMap is partial. Emitted, but not complete for every projection shape.
  • Text ranking differs. The result set matches; the order for a broad search can differ.