Sanity TypeGen
How to use Sanity TypeGen tooling to generate TypeScript definitions from Sanity Studio schemas and GROQ queries.
If you use TypeScript for your frontend or web application, you can type the content you fetch from the Sanity Content Lake API. With the Sanity TypeGen tooling, you can generate type definitions from the schema types in your studio and from the results of your GROQ queries in your frontends and applications.
Typing your content is useful for:
- Catching bugs and errors caused by wrongly handled data types, like forgetting to check for
nullor aundefinedproperty - Autocomplete of fields available in the result of your GROQ query
- Making it easier to refactor integration code when you make changes to the schema
This article covers the different aspects of type generation and walks you through workflows for different project structures: separate repositories, monorepos, and embedded studios.
Requirements
- Sanity Studio v6.6.0 or later
- A Sanity Studio project with a schema
- GROQ queries assigned to variables and written with the groq template literal or
defineQuery(fromgroq,next-sanity, or@sanity/sveltekit)
Overview
You can use Sanity TypeGen to generate types for your Sanity Studio schema and for the return value of a GROQ query run against documents made from that schema.
Types from your schema can be useful for cases where GROQ isn't used, such as Studio customization and schema change management.
The most common use case is generating types for GROQ queries. TypeGen works by "overlaying the schema types" over the GROQ query to determine what types the returned data will have.
Using GraphQL?
If you primarily use the Sanity GraphQL API, we recommend using established GraphQL TypeScript tooling, like GraphQL Code Generator. You can use your GraphQL API URL as the configuration setting for schema.
Minimal example
Sanity TypeGen needs to access a static representation of your Studio schema to generate types. You can use the sanity schema extract command to create the schema.json file the TypeGen command requires:
$ cd ~/my-studio-folder
$ npx sanity schema extract # outputs a `schema.json` file
✔ Extracted schema to /Users/dev/my-studio-folder/schema.json
$ npx sanity typegen generate
✔ Config loaded from sanity.cli.ts
✔ Schema loaded from ./schema.json
✔ Successfully generated types to ./sanity.types.ts in 225ms
└─ 2 queries and 2 schema types
└─ found queries in 1 file after evaluating 1 file
└─ formatted the generated code with prettierTypes from schemas
Take this schema type for "event" documents:
TypeGen follows the casing style of each query variable when it names the result type: postQuery becomes PostQueryResult, and POST_QUERY becomes POST_QUERY_RESULT. It also quotes keys that aren't valid TypeScript identifiers. For example, a projection key like "my-field" is generated as "my-field": string | null.
Input
export const event = defineType({
name: 'event',
type: 'document',
title: 'Event',
fields: [
defineField({
name: 'name',
type: 'string',
title: 'Event name',
validation: rule => rule.required()
}),
defineField({
name: 'description',
type: 'text',
title: 'Event description'
})
]
})Generated types
export type Event = {
_id: string;
_type: 'event';
_createdAt: string;
_updatedAt: string;
_rev: string;
name?: string;
description?: string
}Supported schema types
Nearly all schema types and all permutations of schema types are supported:
- Document types
- Literal fields (boolean, string, text, number, date, dateTime)
- Object types, including geopoint
- Array of types
- Portable Text (Block content)
- References
- Image and file assets
Unsupported schema that will be typed as unknown:
- Cross-dataset references
- Global document references
☝ Is missing support for certain schema types blocking you? Let us know!
Supported schema features
Because studio schemas are defined in JavaScript, TypeGen can't statically generate TypeScript definitions from every configuration option. TypeGen supports the following schema configuration options.
Required field validation and non-optional fields
If you add validation: rule => rule.required() to a field, you might want to translate required rules into non-optional types depending on your use case. You do this by adding the --enforce-required-fields flag when extracting the schema:
$ npx sanity schema extract --enforce-required-fields
✔ Extracted schema to /Users/dev/my-studio-folder/schema.json with enforced required fields
$ npx sanity typegen generate
✔ ...Gotcha
If you have enabled previews of unpublished content, then remember that values might be undefined or null even though the field is set as required. Validation is only checked for published documents, and draft documents are allowed to be in an "invalid" state.
"Built-in" fields required for documents in the Sanity Content Lake will also be set to required in the TypeScript definition: _id, _type, _createdAt, _updatedAt, _rev.
Literals and options.list
The string schema type supports adding a list of predefined values in options.list. This gets generated into a literal type in the TypeScript definition. The generated types in this example come from a schema extracted with --enforce-required-fields, which is why name is non-optional:
Input
export const event = defineType({
name: 'event',
type: 'document',
title: 'Event',
fields: [
defineField({
name: 'name',
type: 'string',
title: 'Event name',
validation: rule => rule.required()
}),
defineField({
name: 'description',
type: 'text',
title: 'Event description'
}),
defineField({
name: 'format',
type: 'string',
title: 'Event format',
options: {
list: ['in-person', 'virtual'],
layout: 'radio',
},
}),
]
})Generated types
export type Event = {
_id: string;
_type: 'event';
_createdAt: string;
_updatedAt: string;
_rev: string;
name: string;
description?: string;
format?: 'in-person' | 'virtual';
}Types from GROQ queries
Sanity TypeGen can also generate TypeScript definitions for GROQ query results. This is useful since GROQ is a query language that lets you specify which fields to return in projections and re-shape that data to fit your needs.
The CLI command requires that a GROQ query is:
- Assigned to a variable (it does not need to be exported)
- Uses the
groqtemplate literal, ordefineQuery, from the groq package (also exported by next-sanity) - Validates as a GROQ expression
The typegen command requires every query to be assigned to a variable whose name is unique across all files. Inline queries aren't assigned to a variable, so they aren't included in the generated types.
import {createClient} from '@sanity/client'
import groq, {defineQuery} from 'groq'
const client = createClient({
projectId: '<your-project-id>', // replace with your project ID
dataset: 'production',
apiVersion: '2026-10-28',
useCdn: true,
})
// ✅ Will be included
async function getStuff() {
const myUniquelyNamedQuery = groq`*[_type == 'post']{ slug, title }`
const result = await client.fetch(myUniquelyNamedQuery)
return result
}
async function getMoreStuff() {
const myOtherUniquelyNamedQuery = defineQuery(`*[_type == 'post']{ slug, title }`)
const result = await client.fetch(myOtherUniquelyNamedQuery)
return result
}
// ❌ Will not be included
async function getInlineStuff() {
const result = await client.fetch(groq`*[_type == 'post']{ slug, title }`)
return result
}Supported query patterns
TypeGen reads your source files without running them. To build each query, it follows every value interpolated into the template literal back to where that value is defined. TypeGen can resolve these values:
- String, number, and Boolean literals, and variables that hold them
- Other query strings and GROQ fragments, such as a shared projection
- Object properties, with dot notation (
DOC_TYPES.PAGE) or a string key (DOC_TYPES['PAGE']), including objects declaredas const - Calls to arrow functions that return a template literal, when every argument is a literal
- Values imported from other files in your project
TypeGen can't resolve these values:
- Conditional (ternary) expressions
- Strings joined with the
+operator - Array elements, such as
TYPES[0] - Function calls with a variable as an argument, such as
byType(POST_TYPE) - Functions with a block body, including
functiondeclarations - Renamed imports, such as
import {POST_TYPE as TYPE}orimport {defineQuery as dq} - Values that only exist at runtime, such as environment variables
The following file shows queries TypeGen resolves and queries it can't:
import {defineQuery} from 'groq'
const DOC_TYPES = {PAGE: 'page', POST: 'post'} as const
const POST_TYPE = DOC_TYPES.POST
const isPage = true
const byType = (type: string) => `_type == "${type}"`
// ✅ Resolved
export const pageQuery = defineQuery(`*[_type == "${DOC_TYPES.PAGE}"]`)
export const postQuery = defineQuery(`*[${byType('post')}]`)
// ❌ Not resolved
export const typedQuery = defineQuery(`*[${byType(POST_TYPE)}]`)
export const pageOrPostQuery = defineQuery(`*[_type == "${isPage ? 'page' : 'post'}"]`)When TypeGen can't resolve a query, it skips that query and prints an error. The error names the query variable and the file and, for most expressions, the line and column of the expression it couldn't resolve. Types for your other queries still generate:
$ npx sanity typegen generate
✖ Error while extracting query from variable 'typedQuery' in ./src/sanity.queries.ts: Could not find binding for node "type" in ./src/sanity.queries.ts:6:46
✖ Error while extracting query from variable 'pageOrPostQuery' in ./src/sanity.queries.ts: Unsupported expression type: ConditionalExpression in ./src/sanity.queries.ts:14:58
⚠ Encountered errors in 1 file while generating types
✔ Successfully generated types to ./sanity.types.ts in 324ms
└─ 2 queries and 0 schema typesTo fix a query TypeGen can't resolve, replace the value with a constant TypeGen can follow. For values that change at runtime, use a GROQ parameter such as $type in the query and pass the value when you fetch.
// ❌ TypeGen can't follow POST_TYPE into byType()
export const typedQuery = defineQuery(`*[${byType(POST_TYPE)}]`)// ✅ Use a GROQ parameter, and pass the value when you fetch
export const typedQuery = defineQuery(`*[_type == $type]`)
const posts = await client.fetch(typedQuery, {type: POST_TYPE})Supported GROQ features
Since GROQ is so versatile, we are still working on identifying edge cases, and some functions are not yet supported.
Unsupported expressions
Unsupported GROQ expressions will be typed as unknown by TypeGen.
Supported features:
- Data types: Null, Boolean, Number, String, Array, Object
- Selectors: Everything (
*), this (@), attribute filters ([name == "string"]), parent (^) - Functions:
coalesce(),dateTime::now(),global::now(),round(),upper(),lower(),select()(and=>), and array functions - Compounds: parentheses, traversals (
[]), pipe function calls (|) - Operators: and (
&&), or (||), not (!), equality (==, !=), comparison (<, <=, >, >=), plus (+), minus (-), unaries (+-), star (*), slash (/), percent (%), star star (**)
☝ Is missing support for certain GROQ features blocking you? Let us know in the community!
Supported file types
TypeGen parses queries from multiple file types:
- TypeScript (.ts, .tsx)
- JavaScript (.js, .jsx, .mjs, .cjs)
- Astro (.astro)
- Svelte (.svelte)
- Vue (.vue)
SvelteKit's defineQuery function is fully supported for automatic type inference.
Automatic Sanity client type inference
When you write your GROQ queries with defineQuery, the Sanity client automatically returns types when you pass the query to fetch, after you run sanity typegen generate.
Example: automatic type inference
import { defineQuery } from 'groq'
export const postsQuery = defineQuery(`*[_type == "event"]{name}`)import { createClient } from '@sanity/client'
import { postsQuery } from './sanity.queries'
const client = createClient({
projectId: '<your-project-id>', // replace with your project ID
dataset: 'production',
apiVersion: '2026-10-28',
useCdn: true,
})
export function getPosts() {
return client.fetch(postsQuery) // <- the returned type here is automatically inferred
}Gotcha
For TypeScript to return the query types generated by TypeGen, the generated sanity.types.ts needs to be included in the pattern configured in the include array in tsconfig.json.
Opt out of automatic type inference
You can opt out by setting typegen.overloadClientMethods to false in your sanity.cli.ts.
Ignoring individual queries
You can instruct the type generator to skip generating query types for individual queries by having @sanity-typegen-ignore in a leading comment before the query, similar to how ESLint and TypeScript can be instructed.
Example: ignore a query
Input
import groq from 'groq'
// import { groq } from 'next-sanity'
const postQuery = groq`*[_type == "event"]{name}`
const authorQuery = groq`*[_type == "author" && name == $name][0]{_type, name, description}`
// this query won't get generated types because of the instruction below
// @sanity-typegen-ignore
const anotherQuery = groq`*[_type == "another"][0]`Generated types
// Variable: postQuery
// Query: *[_type == "event"]{name}
export type PostQueryResult = Array<{
name: string | null
}>
// Variable: authorQuery
// Query: *[_type == "author" && name == $name][0]{_type, name, description}
export type AuthorQueryResult = {
_type: 'author'
name: string | null
description: string | null
} | nullType utilities
TypeGen provides utility types to help you work with generated types in complex schemas.
Get utility
The Get utility extracts deeply nested properties from your types, supporting up to 20 levels of nesting. This is especially useful for page builder schemas with complex nested structures.
import type { Get } from '@sanity/codegen'
import type { Page } from './sanity.types'
// Extract a deeply nested property type
type HeroImageAlt = Get<Page, 'hero', 'image', 'alt'>
// Use `number` to step into an array
type Section = Get<Page, 'sections', number>This replaces verbose type manipulation with NonNullable and index access.
FilterByType utility
The FilterByType utility filters specific types from union types using the _type discriminator. This is useful when working with arrays of different block types.
import type { FilterByType } from '@sanity/codegen'
import type { PageBuilder } from './sanity.types'
// Extract only hero blocks from a union
type HeroBlock = FilterByType<PageBuilder, 'hero'>
// Works with multiple types
type ContentBlocks = FilterByType<PageBuilder, 'hero' | 'textBlock' | 'imageGallery'>Type generation workflows
You generate types from your schemas and GROQ queries with the Sanity CLI, in two steps. First, you run sanity schema extract in the root folder of your studio project to extract the structure of your schemas into a JSON file. Then you run sanity typegen generate to generate type definitions from that JSON file.
Automatic type generation
Automatic type generation is the recommended workflow. Enable it in your sanity.cli.ts configuration, and types regenerate automatically during development.
// sanity.cli.ts
import { defineCliConfig } from 'sanity/cli'
export default defineCliConfig({
typegen: {
enabled: true,
},
})With enabled set to true, types regenerate automatically when you run sanity dev or sanity build. You can still use manual commands when needed.
Understanding configuration options
typegen.enabled: true activates automatic type generation during sanity dev or sanity build. Similarly, schemaExtraction.enabled: true activates automatic schema extraction (generating schema.json).
In the Studio: You typically want schema extraction enabled. Enable typegen too if you need types in your Studio code (for custom components, etc.).
In separate frontend repositories: Use sanity typegen generate --watch directly, since you're not using sanity dev/build to build your frontend.
General workflow
- Extract current schema with
sanity schema extract - Generate types from schemas and queries with
sanity typegen generate
Extracting Studio schema to schema.json
The first step towards generating type definitions based on your schemas is to extract the entire schema structure into a single JSON file for the typegen command to ingest.
👉 In your Sanity project root (wherever your sanity.config.ts lives), run the following CLI command:
$ npx sanity schema extract
✔ Extracted schema to /Users/dev/my-studio-folder/schema.json
# If you have multiple workspaces defined, specify which one to use:
$ npx sanity schema extract --workspace=commerce
✔ Extracted schema to /Users/dev/my-studio-folder/schema.jsonThe CLI picks up the schema definition from your project configuration and writes a representation of your complete schema structure to a new file named schema.json, unless you set another output path with the --path flag.
🔗 Learn how to override the default output file and more in the Schemas CLI command reference.
Generate sanity.types.ts from schema.json
After you extract your schema to schema.json with sanity schema extract, you can generate types.
👉 Still in your Sanity project root, run the following command in your CLI:
$ npx sanity typegen generate
✔ ...The CLI reads the schema.json file created by sanity schema extract and creates a new file, by default named sanity.types.ts, containing all the type declarations for your schema and for any GROQ query found in the default source paths: ./src, ./app, and ./sanity.
The generate command can be configured by adding a property named typegen to your sanity.cli.ts/js containing a configuration object with the following shape:
import { defineCliConfig } from 'sanity/cli';
export default defineCliConfig({
// ...rest of config
typegen: {
path: [
"./src/**/*.{ts,tsx,js,jsx,mjs,cjs,astro,vue,svelte}",
"./app/**/*.{ts,tsx,js,jsx,mjs,cjs,astro,vue,svelte}",
"./sanity/**/*.{ts,tsx,js,jsx,mjs,cjs}",
], // glob patterns for the files that contain your queries. Can also be a single string
schema: "./schema.json", // path to your schema file, generated with 'sanity schema extract' command
generates: "./sanity.types.ts", // path to the output file for generated type definitions
overloadClientMethods: true, // set to false to disable automatic overloading the sanity client
},
})The example above is shown with the default values. Note that the path can be defined as either an array of strings or a single string.
Deprecated typegen config file
Type generation was previously configured in a separate configuration file (typically sanity-typegen.json). This has been deprecated and we advise everyone to move their configuration into the main CLI configuration (typically found in sanity.cli.ts).
Watch mode
Both sanity schema extract and sanity typegen generate feature a --watch flag (from v5.8.0) to watch for changes in your files and generate the schema and type files as you change things in your project.
The schema extraction watch mode watches for changes in your Studio and generates the schema.json file.
The type generation watches the configured schema path and files you've configured your type generation to look for.
$ cd ~/frontend
$ sanity typegen generate --watch
✔ Config loaded from sanity.cli.ts
✔ Schema loaded from ./schema.json
✔ Successfully generated types to ./sanity.types.ts in 328ms
└─ 0 queries and 18 schema types
└─ found queries in 0 files after evaluating 1 file
└─ formatted the generated code with prettier
[5:42:03 PM] change: src/queries.ts
✔ Schema loaded from ./schema.json
✔ Successfully generated types to ./sanity.types.ts in 414ms
└─ 1 query and 18 schema types
└─ found queries in 1 file after evaluating 1 file
└─ formatted the generated code with prettierGenerated files and version control
Commit schema.json and sanity.types.ts to your repository. Both files are generated, but tracking them means every developer, CI job, and type check starts from the same types without running the CLI first.
Sanity's starter templates commit these files. They also regenerate them before the dev and build scripts run, so the committed types stay in step with the schema. The --force flag lets sanity schema extract overwrite the committed schema.json without prompting. Without it, the command asks for confirmation locally and exits with an error in CI:
{
"scripts": {
"typegen": "sanity schema extract --force && sanity typegen generate",
"predev": "npm run typegen",
"prebuild": "npm run typegen"
}
}Teams working in the same schema often hit merge conflicts in sanity.types.ts. Because the file is regenerated in full on every run, you never have to resolve one by hand. Take either side of the conflict, then regenerate:
$ npx sanity schema extract --force
$ npx sanity typegen generateIgnoring the generated files
You can add schema.json and sanity.types.ts to .gitignore instead. Every environment that builds or type-checks your code then has to run sanity schema extract and sanity typegen generate first, including CI and any teammate who has cloned the repository.
Example: embedded Studio
A common pattern is to embed Sanity Studio in another application, keeping everything in a single repository. You can find several example repositories that follow this convention in the templates section of the Sanity Exchange. Because your studio and application share a single project root, this is the most straightforward setup. The only adjustment you might need is to set typegen.path to where your queries live. If your queries are in ./src, ./app, or ./sanity, the default settings already cover them.
Example: monorepo
Another common way of structuring a Sanity-powered project is to create a "monorepo" within which your Studio and your consuming application live separately side by side, possibly in sub-repositories of their own. Depending on the needs and preferences of your project you could use the configuration options of each CLI command to output the generated files into your consuming application, or you could keep the generated files in the Studio folder and put it on the application to find them by traversing the monorepo.
# Use the --path flag to output schema.json elsewhere
npx sanity schema extract --path ../../my-cool-app/sanity-schemas.json# Use the --path flag to output schema.json elsewhere
pnpm dlx sanity schema extract --path ../../my-cool-app/sanity-schemas.json# Use the --path flag to output schema.json elsewhere
yarn dlx sanity schema extract --path ../../my-cool-app/sanity-schemas.json# Use the --path flag to output schema.json elsewhere
bunx sanity schema extract --path ../../my-cool-app/sanity-schemas.jsonimport { defineCliConfig } from 'sanity/cli';
export default defineCliConfig({
// ...rest of config
typegen: {
path: "../../my-cool-app/src/**/*.{ts,tsx,js,jsx}", // glob pattern to your typescript files
schema: "../../my-cool-app/sanity-schemas.json", // path to your schema file, generated with 'sanity schema extract' command
generates: "../../my-cool-app/sanity.types.ts" // path to the output file for generated type definitions
},
})Example: separate repos
Use this recipe when your consumer (frontend or app) lives in a different repository from your Studio. The consumer repo runs TypeGen against a schema.json file extracted from the Studio.
In the Studio repo: extract the schema
Run sanity schema extract and write the output to a path your frontend can read:
npx sanity schema extract --path ../my-frontend/schema.json --forcepnpm dlx sanity schema extract --path ../my-frontend/schema.json --forceyarn dlx sanity schema extract --path ../my-frontend/schema.json --forcebunx sanity schema extract --path ../my-frontend/schema.json --forceFor automated workflows, run this command in CI on every Studio deploy, or publish schema.json through a shared internal package.
In the frontend repo: install and configure
Install the required packages. The full sanity package is required for the CLI commands. Add it to devDependencies so it stays out of your runtime bundle:
npm install --save-dev sanitypnpm add --save-dev sanityyarn add --dev sanitybun add --dev sanityCreate sanity.cli.ts at the root of your frontend repo and configure the typegen block:
import {defineCliConfig} from 'sanity/cli'
export default defineCliConfig({
typegen: {
path: './src/**/*.{ts,tsx,js,jsx}',
schema: './schema.json',
generates: './sanity.types.ts',
},
})Generate types. Use watch mode in development to regenerate types as your queries change:
# Development: regenerate on save
npx sanity typegen generate --watch
# CI: run once
npx sanity typegen generate# Development: regenerate on save
pnpm dlx sanity typegen generate --watch
# CI: run once
pnpm dlx sanity typegen generate# Development: regenerate on save
yarn dlx sanity typegen generate --watch
# CI: run once
yarn dlx sanity typegen generate# Development: regenerate on save
bunx sanity typegen generate --watch
# CI: run once
bunx sanity typegen generateThe integrated TypeGen feature and the --watch flag both require Sanity 5.8.0 or higher. Version 5.10.0 or higher is recommended (TypeGen GA release).
Was this page helpful?
Feedback submitted, thanks for helping us improve the docs. Have you tried these alternatives?