Structured Content
GROQ
Lesson 2 of 5
What you'll learn
- Filter documents with
*[...]predicates - Shape results with projections, including renamed and computed fields
- Join across documents with the
->dereference operator - Order, slice, and parameterize queries safely
GROQ (Graph-Relational Object Queries) treats your whole dataset as one big JSON collection. * means "every document"; the square brackets that follow are a filter. This returns every published-style post that has a slug:
*[_type == "post" && defined(slug.current)]
By default you get whole documents back. A projection — curly braces after the filter — shapes each result, and can rename or compute fields:
*[_type == "post"]{
title,
"slug": slug.current,
"excerpt": pt::text(body)[0...160]
}
Joins are just an arrow
A reference field is a pointer: {_ref: "author-id"}. The -> operator dereferences it — follows the pointer and returns the referenced document, which you can project in turn:
*[_type == "post"]{
title,
author->{name, "avatar": image.asset->url}
}
That's the whole join story. No join tables, no ORM — the arrow works anywhere a reference appears, and chains (image.asset->url follows the image's asset reference).
Ordering and slicing are pipeline operations. order() sorts, and a range slices — [0...3] is the first three, [0] unwraps a single result out of the array:
*[_type == "post"] | order(publishedAt desc)[0...3]
Finally, params. Never string-interpolate user input into a query — pass $-prefixed parameters alongside it instead:
*[_type == "post" && slug.current == $slug][0]
Params are not just hygiene
Interpolated queries are an injection risk and a cache killer — every distinct string is a new query to the CDN. Parameterized queries stay safe and cacheable.
Because GROQ is "filter, shape, follow pointers, sort, slice" over JSON, it models directly as array methods. The challenge implements the query in the comment — dereference included — over an inline dataset.
Run it. Each chained array method maps to one GROQ clause — the comments show which. Try changing the slice to [0...1] or the sort to asc.
What does the -> operator do in a GROQ query?
Next: getting these queries into a Next.js app — the next-sanity client, cached fetches, and revalidation.
Saved on this device. Sign in to sync your progress everywhere.