Content Lake (Datastore)

GROQ for GraphQL developers

Why don't we use GraphQL as our primary query language, and what to do if you want a GraphQL API on your data

If you're familiar with GraphQL, GROQ will feel familiar in some places and different in others. This page maps common GraphQL patterns to their GROQ equivalents and highlights what GROQ adds beyond what the GraphQL spec describes. If you want to use Sanity's GraphQL API instead, see the GraphQL guide.

GROQ is the native query language for the Sanity Content Lake. GraphQL is a generated, typed layer based on your schema. This guide is here to help GraphQL users decide whether to switch and to translate what they already know.

GraphQL concepts in GROQ

Fetch by ID

In GraphQL, you call a schema-declared root field. In GROQ, you filter the document space and project the fields you want. Any field can be the lookup key, not only _id.

Field selection

GraphQL selection sets become GROQ projections. GROQ lets you rename fields inline ("heading": title) and spread the rest with ....

Filtering

GraphQL where arguments are schema-declared. GROQ filters are arbitrary boolean expressions inside [ ]. You can combine equality, comparison, match, in, defined(), references(), and date math without changing the schema.

Reference traversal

GraphQL traverses references through nested selection on schema-declared relation fields. GROQ uses the -> operator on any field that contains a _ref value, with no schema declaration required. Chains compose: author->company->address.

Pagination

Sanity's GraphQL API exposes limit and offset arguments on list fields. GROQ uses slice syntax, which works on any array, including sub-arrays inside projections.

Ordering

GraphQL passes sort arguments to a list field. GROQ uses the | order() pipe function, which supports multi-key ordering and inline transforms such as lower(title) for case-insensitive sorting.

Aggregation

GraphQL has no aggregation in the query language. GROQ has count(), math::sum(), math::avg(), math::min(), and math::max() as first-class functions you can inline in projections. Neither language has a native group-by.

Real-time updates

GraphQL subscriptions are schema-declared and limited to a single root field per operation. Sanity does not support GraphQL subscriptions, but offers two real-time options driven by GROQ filters: the Listen API for server-sent change events on any GROQ filter, and the Live Content API, which handles caching and reconnection for production workloads.

Typed queries

GraphQL fragments plus a codegen pipeline give you typed clients. The equivalent in Sanity is defineQuery() from the groq package combined with Sanity TypeGen, which generates TypeScript result types from your queries automatically. Query reuse in GROQ is achieved with string composition or GROQ custom functions.

What GROQ adds beyond GraphQL

Some capabilities are inline in GROQ that would require server-side resolver code in a GraphQL implementation, and one is specific to Sanity's platform regardless of the query language.

Inline computed fields

GraphQL is not a computation language; computed values must be defined as schema fields with server-side resolvers. GROQ lets you compute values directly in the projection with coalesce(), select(), string functions, math functions, and arbitrary expressions.

Dynamic reference traversal

GraphQL requires every join point to be a schema-declared field with a resolver. GROQ traverses any _ref value at query time, with no schema changes or server redeploy. Adding a new traversal is a query edit, not a deployment.

Reverse lookups in projections

GROQ's ^ parent reference and references() function let you express a reverse join inside a projection, without a schema-declared reverse relation. In GraphQL, the same query requires a schema field with a resolver that searches for documents referring to the current one.

Full-text and semantic search

GROQ provides text::query() for structured text search with phrase, prefix, and negation syntax, and text::semanticSimilarity() for vector-based ranking. GraphQL has no text or semantic search in the spec; implementations need custom resolvers or external integrations. Semantic similarity requires dataset embeddings to be enabled.

Cross-dataset references

Sanity's cross-dataset references let a document in one dataset link to a document in another. GROQ traverses them with the same -> operator. Sanity's GraphQL API does not support cross-dataset references; only GROQ can dereference them. Cross-dataset references are available on the Enterprise plan.

Mutations

Sanity's GraphQL API does not support mutations. Writes go through the Mutation API (or the equivalent client methods such as client.create(), client.patch(), and client.delete()). This is the same for GROQ users and GraphQL users; mutations are a separate concern from queries.

When to keep GraphQL

Some projects are better served by Sanity's GraphQL API:

  • You have an existing investment in GraphQL tooling such as Apollo Client, Relay, or established codegen pipelines that you want to keep using.
  • Your team has deep GraphQL expertise and the cost of switching mental models would outweigh the gain.

See the GraphQL guide for setup, deployment, perspectives, security, and the full filter and sort reference.

Next steps

Was this page helpful?