GROQ (Graph-Oriented Query Language) is Sanity’s powerful query language for retrieving and transforming content from your Content Lake.
What is GROQ?
GROQ is designed for querying JSON documents with a syntax that:
- Filters documents based on conditions
- Projects specific fields from documents
- Joins data from referenced documents
- Transforms data with expressions and functions
- Sorts and slices result sets
Using the client in Studio
The useClient hook provides a configured Sanity client:
import {useClient} from 'sanity'
function MyComponent() {
const client = useClient({apiVersion: '2025-02-07'})
// Use client to query
const fetchAuthors = async () => {
const authors = await client.fetch(
`*[_type == "author"]`
)
return authors
}
}
Always specify an apiVersion when using useClient to prevent breaking changes.
Basic GROQ syntax
Fetch all documents of a specific type:
This returns all author documents.
Select only the fields you need:
*[_type == "author"] {
name,
role,
"imageUrl": image.asset->url
}
*[_type == "author" && role == "developer"]
Order results by a field:
*[_type == "author"] | order(name asc)
Common query patterns
Single document
With references
Array filtering
import {useClient} from 'sanity'
function AuthorProfile({authorId}: {authorId: string}) {
const client = useClient({apiVersion: '2025-02-07'})
const fetchAuthor = async () => {
const author = await client.fetch(
`*[_type == "author" && _id == $id][0] {
name,
role,
image,
"bookCount": count(*[_type == "book" && references(^._id)])
}`,
{id: authorId}
)
return author
}
}
const query = `*[_type == "book"] {
title,
publicationYear,
author->{
name,
role,
image
},
"coverUrl": coverImage.asset->url
}`
const books = await client.fetch(query)
const query = `*[_type == "author"] {
name,
"publishedBooks": *[_type == "book" && references(^._id) && publicationYear > 2020] {
title,
publicationYear
}
}`
const authorsWithRecentBooks = await client.fetch(query)
Reference following
GROQ makes it easy to follow references between documents:
*[_type == "book"] {
title,
author->{
name,
role,
"bestFriend": bestFriend->name
}
}
The -> operator dereferences a reference field.
Reshape your data in the query:
*[_type == "author"] {
"authorName": name,
"isDesigner": role == "designer",
"fullProfile": {
"name": name,
"role": role,
"hasImage": defined(image)
}
}
Functions in GROQ
GROQ provides useful functions:
*[_type == "author"] {
name,
"uppercase": upper(name),
"lowercase": lower(name),
"length": length(name)
}
*[_type == "author"] {
name,
awards,
"awardCount": count(awards),
"hasAwards": count(awards) > 0
}
*[_type == "book"] {
title,
"status": select(
publicationYear > 2023 => "new",
publicationYear > 2020 => "recent",
"classic"
)
}
Parameters in queries
Use parameters for dynamic queries:
import {useClient} from 'sanity'
const client = useClient({apiVersion: '2025-02-07'})
const query = `*[_type == "book" && publicationYear >= $year] {
title,
publicationYear,
author->name
}`
const recentBooks = await client.fetch(query, {
year: 2020
})
Always use parameters instead of string interpolation to prevent GROQ injection vulnerabilities.
Ordering and slicing
Control result ordering and pagination:
*[_type == "author"] | order(name asc) [0..10] {
name,
role
}
This returns the first 10 authors sorted by name.
Counting and aggregation
{
"totalAuthors": count(*[_type == "author"]),
"developers": count(*[_type == "author" && role == "developer"]),
"designers": count(*[_type == "author" && role == "designer"])
}
Using the references function
Find documents that reference another document:
*[_type == "book" && references($authorId)] {
title,
publicationYear
}
Coalesce for fallbacks
Provide fallback values:
*[_type == "author"] {
name,
"displayName": coalesce(nickname, name, "Unknown"),
"imageUrl": coalesce(image.asset->url, "/default-avatar.png")
}
Portable Text queries
Query rich text content:
*[_type == "post"] {
title,
"excerpt": pt::text(body),
"plainText": array::join(body[].children[].text, " ")
}
Using in React hooks
Combine with React hooks for data fetching:
import {useEffect, useState} from 'react'
import {useClient} from 'sanity'
function AuthorList() {
const client = useClient({apiVersion: '2025-02-07'})
const [authors, setAuthors] = useState([])
useEffect(() => {
const query = `*[_type == "author"] | order(name asc) {
_id,
name,
role,
image
}`
client.fetch(query).then(setAuthors)
}, [client])
return (
<ul>
{authors.map((author) => (
<li key={author._id}>{author.name}</li>
))}
</ul>
)
}
Real-time listening
Listen to document changes:
import {useEffect, useState} from 'react'
import {useClient} from 'sanity'
function LiveAuthorCount() {
const client = useClient({apiVersion: '2025-02-07'})
const [count, setCount] = useState(0)
useEffect(() => {
const query = `count(*[_type == "author"])`
const subscription = client.listen(query).subscribe((update) => {
client.fetch(query).then(setCount)
})
return () => subscription.unsubscribe()
}, [client])
return <div>Authors: {count}</div>
}
Testing queries in Vision
Use the Vision plugin to test queries:
import {defineConfig} from 'sanity'
import {visionTool} from '@sanity/vision'
export default defineConfig({
// ... project settings
plugins: [
visionTool(),
],
})
Vision provides a GROQ playground in your studio for testing queries interactively.
Common patterns
- Project early: Only select fields you need
- Use parameters: Leverage query caching
- Limit results: Always paginate large result sets
- Index carefully: Ensure frequently queried fields are indexed
- Avoid deep nesting: Limit reference depth when possible
GROQ queries are cached by the API. Identical queries with the same parameters will be served from cache.
Next steps