This document provides schema design examples and corresponding GROQ queries for various use cases. It’s a practical guide for developers to get familiar with GROQ querying and the basics of Sanity schema design.
Use the following query to retrieve a list of all unique top-level types in your dataset:
array::unique(*[_type != null]._type) | order(@ asc)
*[_type != null]
: Selects all documents with a _type field.._type
: Projects only the _type field for each document.array::unique()
: Filters the results to include only unique values.| order(@ asc)
: Sorts the unique types alphabetically.
[
"author",
"blogPost",
"category",
"product",
"tag"
]
- Dynamic Schema Analysis:
- Sanity datasets are schemaless at runtime, so the query analyzes the actual data to infer structure.
- Discover All Types:
- The query identifies all unique document types in your dataset.
import { defineType, defineField } from 'sanity';
export default defineType({
name: 'category',
type: 'document',
title: 'Category',
fields: [
defineField({
name: 'name',
type: 'string',
title: 'Name',
}),
],
});
import { defineType, defineField } from 'sanity';
export default defineType({
name: 'tag',
type: 'document',
title: 'Tag',
fields: [
defineField({
name: 'label',
type: 'string',
title: 'Label',
}),
],
});
import { defineType, defineField } from 'sanity';
export default defineType({
name: 'product',
type: 'document',
title: 'Product',
fields: [
defineField({
name: 'title',
type: 'string',
title: 'Title',
}),
defineField({
name: 'price',
type: 'number',
title: 'Price',
}),
defineField({
name: 'isAvailable',
type: 'boolean',
title: 'Available',
}),
defineField({
name: 'features',
type: 'array',
title: 'Features',
of: [{ type: 'string' }],
}),
defineField({
name: 'category',
type: 'reference',
title: 'Category',
to: [{ type: 'category' }],
}),
defineField({
name: 'tags',
type: 'array',
title: 'Tags',
of: [{ type: 'reference', to: [{ type: 'tag' }] }],
}),
],
});
*[_type == "product" && isAvailable == true] | order(price desc) {
title,
price
}
- Filter:
[_type == "product" && isAvailable == true]
retrieves available products. - Sorting:
order(price desc)
sorts results by price in descending order. - Projection:
{ title, price }
specifies which fields to return.
*[_type == "product"] {
title,
"categoryName": category->name
}
- Join:
category->name
retrieves thename
field from the referencedcategory
document. - Projection: Adds
"categoryName"
as a computed field in the output.
*[_type == "product"] {
title,
category->
}
- Expands the entire
category
document, returning all its fields inline.
*[_type == "product"] {
title,
"tags": tags[]->label
}
tags[]->label
expands the array of references, retrieving only thelabel
field.
*[_type == "product" && "tag-123" in tags[]._ref] {
title
}
- Filters products containing a reference to the tag with
_id: "tag-123"
.
*[_type == "category"].name
- Returns only the
name
field for all categories, skipping the usual{}
projection syntax.
*[_type == "category"] {
name,
"products": *[_type == "product" && references(^._id)]{ title }
}
- Embedded query fetches all products associated with a category.
*[_type == "product"] | order(price desc)[0...5] {
title,
price
}
- Pipes data to
order
and[0...5]
to retrieve the top 5 most expensive products.
*[_type == "product"] {
title,
features[0...2]
}
- Retrieves the first two elements of the
features
array.
*[_type == "product"] {
...,
"priceWithTax": price * 1.1
}
...
includes all fields, while adding a computed field for price with tax.
count(*[_type == "product" && isAvailable == true])
- Returns the count of available products without returning their details.
import { defineType, defineField } from 'sanity';
export default defineType({
name: 'author',
type: 'document',
title: 'Author',
fields: [
defineField({
name: 'name',
type: 'string',
title: 'Name',
}),
defineField({
name: 'bio',
type: 'text',
title: 'Bio',
}),
],
});
import { defineType, defineField } from 'sanity';
export default defineType({
name: 'blogPost',
type: 'document',
title: 'Blog Post',
fields: [
defineField({
name: 'title',
type: 'string',
title: 'Title',
}),
defineField({
name: 'author',
type: 'reference',
title: 'Author',
to: [{ type: 'author' }],
}),
],
});
*[_type == "blogPost"] {
title,
author->{
name,
bio
}
}
- Sub-projection
{ name, bio }
withinauthor
fetches specific fields.
*[_type == "blogPost"] {
title,
author->{
name,
"posts": *[_type == "blogPost" && author._ref == ^._id]{ title }
}
}
- Fetches blog posts by the same author using a nested query.