Sanity TypeGen
How to use Sanity TypeGen tooling to generate TypeScript definitions from Sanity Studio schemas and GROQ queries.
Beta feature
Sanity TypeGen is currently in beta, and the APIs described in this article will likely change.
We welcome any feedback!
If you use TypeScript for your front end or web application, you will want to type the content from the Sanity Content Lake API. With the Sanity TypeGen tooling, you can generate type definitions from the schema types in your Studio and the results of your GROQ queries in your front ends and applications.
Typing your content is useful for:
- Catching bugs and errors caused by wrongly handled data types, like forgetting to check for
null
or aundefined
property - 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 will present the different aspects of type generation and walk you through workflows depending on your project structure (separate repositories, monorepos, and embedded Studio).
- Sanity CLI (Command Line Interface), v3.35.0 or later
- A Sanity Studio project with a schema
- GROQ queries assigned to variables and using the groq template string helper
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.
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
.
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
$ sanity schema extract # outputs a `schema.json` file
✔ Extracted schema
$ sanity typegen generate
✔ Generated TypeScript types for 2 schema types and 2 GROQ queries in 1 files into: ./sanity.types.ts
Take this simple schema type for “event” documents:
Schema
// ./src/schema/event.ts
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
// sanity.types.ts
export type Event = {
_id: string;
_type: 'event';
_createdAt: string;
_updatedAt: string;
_rev: string;
name?: string;
description?: string
}
Nearly all schema types and all permutations of schema types are supported:
- Document types
- Literal fields (boolean, string, text, number, geopoint, date, dateTime)
- Object types
- Array of types
- Portable Text (Block content)
- References
- Image and file assets
Unsupported schema that will be typed as unknown
:
- Cross-dataset references
☝ Is missing support for certain schema types blocking you? Let us know!
Since Studio schemas are defined in JavaScript, it can get gnarly to represent and generate statically TypeScript definitions from specific configuration options. However, the following schema configuration options are supported.
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, 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:
Schema
// ./src/schema/event.ts
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
// sanity.types.ts
export type Event = {
_id: string;
_type: 'event';
_createdAt: string;
_updatedAt: string;
_rev: string;
name: string;
description?: 'string';
format?: 'in-person' | 'virtual';
}
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
groq
template literal, ordefineQuery
, from the groq package - Validates as a GROQ expression
The typegen
requires all queries to have a unique name. This also means that no inline queries are included in the generated types.
// ✅ 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 myUniquelyNamedQuery = defineQuery(`*[_type == 'post']{ slug, title }`)
const result = await client.fetch(myUniquelyNamedQuery)
return result
}
// ❌ Will not be included
async function getInlineStuff() {
const result = await client.fetch(groq`*[_type == 'post']{ slug, title }`)
return result
}
Since GROQ is so versatile, we are still working on identifying edge cases, and some functions are not yet supported.
Unsupported GROQ expressions will be typed as unknown
by the TypeGen.
Supported features:
- Data types: Null, Boolean, Number, String, Array, Object
- Selectors: Everything (
*
), this (@
), attribute filters ([name == "string"]
), parent (^
) - Functions:
pt:text()
,coalesce()
,select()
,dateTime::now()
,global::now()
,round()
,upper()
,lower()
,select()
(and=>
), and array functions - Compounds: parenthesis, 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 #typescript channel in the community!
By using defineQuery
when writing your GROQ queries the Sanity Client will automatically return types when the query is used with fetch
, after running sanity typegen generate
.
// sanity.queries.ts
import { defineQuery } from 'groq'
export const postsQuery = defineQuery(`*[_type == "event"]{title}`)
// data.ts
import { createClient } from '@sanity/client'
import { postsQuery } from './sanity.queries.ts'
const client = createClient({...})
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 includes
array in tsconfig.json
.
Opt out of automatic type inference
You can opt out by setting overloadClientMethods
to false
in your sanity-typegen.json
.
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.
Queries
// sanity.queries.ts
import { groq } from 'groq'
// import { groq } from 'next-sanity'
const postQuery = groq`*[_type == "event"]{title}`
const authorQuery = groq`*[_type == "author" && name == $name][0]{_type, name, description}`
// this query wont get generated types because of the instruction below
// @sanity-typegen-ignore
const anotherQuery = groq`*[_type == "another"][0]`
Generated types
// sanity.types.ts
// Variable: postQuery
// Query: *[_type == "event"]{title}
export type PostQueryResult = Array<{
title: string | null
}>
// Variable: authorQuery
// Query: *[_type == "author" && name == $name][0]{name, description}
export type AuthorQuery = {
_type: 'author'
name: string | null
description: string | null
} | null
Generating types from your schemas and GROQ queries using the Sanity CLI in the root Sanity Studio folder for the relevant project. First, you’ll run a command to extract the structure of your schemas into a format suited for further processing and then convert it into a handy JSON file. Then you’ll run another command to generate and output type definitions based on that same JSON file.
- Extract current schema with
sanity schema extract
- Generate types from schemas and queries with
sanity typegen generate
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
# If you have multiple workspaces defined, specify which one to use:
$ npx sanity schema extract --workspace=commerce
✔ Extracted schema
The CLI tool will pick up the schema definition from your project configuration, and generate a representation of your complete schema structure in a new file named schema.json
unless otherwise specified. You are now ready to proceed to the next step.
🔗 Learn how to override the default output file and more in the CLI reference docs.
Once you have extracted your schema as described in the previous section, you are all set to generate some types.
👉 Still in your Sanity project root, run the following command in your CLI:
$ npx sanity typegen generate
✔ ...
The CLI tool will look for the schema.json
file you created in the previous step and will create 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 file path, which is ./src
.
The generate
command can be configured by adding a file named sanity-typegen.json
containing a configuration object with the following shape:
{
"path": "./src/**/*.{ts,tsx,js,jsx}", // glob pattern to your typescript files. Can also be an array of paths
"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.
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. This is the happiest of paths since your studio and application are sharing a single project root. Likely, the only adjustment you might need to make is to specify the path to your queries (unless it’s in a sub-directory of ./src
in which case the default settings have you fully covered!)
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 config options in sanity-typegen.json to change output directory
{
"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
}
You might also find yourself working on a project that keeps separate repos for the studio and consuming applications. Since there is currently no way of accessing generated type definitions through the Sanity Client, you must rely on more rudimentary methods of making types available in your frontends – such as copy and paste, or adapting the monorepo example if your folders have stable paths.