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->nameretrieves thenamefield from the referencedcategorydocument. - Projection: Adds
"categoryName"as a computed field in the output.
*[_type == "product"] {
title,
category->
}- Expands the entire
categorydocument, returning all its fields inline.
*[_type == "product"] {
title,
"tags": tags[]->label
}tags[]->labelexpands the array of references, retrieving only thelabelfield.
*[_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
namefield 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
orderand[0...5]to retrieve the top 5 most expensive products.
*[_type == "product"] {
title,
features[0...2]
}- Retrieves the first two elements of the
featuresarray.
*[_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 }withinauthorfetches 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.