App SDK and TypeGen
Learn how to use Sanity TypeGen with the App SDK for increased type safety and improved developer experience.
This page is for using TypeGen with the App SDK. See the TypeGen article to generate types for your Studio and front end applications.
Sanity TypeGen is a tool that generates TypeScript types directly from your Sanity schemas and GROQ queries. When used with the Sanity App SDK, it provides strong type safety and autocompletion suggestions for your documents, query results, and projections.
In this guide, we’ll walk through setting up and using TypeGen within your SDK app.
Experimental feature
TypeGen support in the App SDK is currently in its early stages. We’re actively working on improving this integration and the developer experience around it. For now, some parts of this process may be suboptimal, but we invite the adventurous among you to follow along!
Setup
Using Typegen involves two main steps: extracting your schema(s) and then generating the types. Both commands are available via the CLI.
Extract schemas
First, you need to extract your Sanity schema(s) into a JSON format that Typegen can understand. Currently, this step relies on the full sanity
package, typically used within your Sanity Studio project, as Typegen needs access to the complete schema definition to generate accurate types.
Schema extraction is performed within your Studio setup to generate the schema.json
file. Once created, this file can be used independently by other tools or parts of your workflow.
We recognize that requiring the Studio environment solely for this generation step isn't ideal, and we're actively working on improving this workflow in future App SDK updates to make the process more self-contained.
Use the sanity schema extract
command within your Studio project or a project that has the sanity
package installed:
npx sanity schema extract --workspace <workspace-name> --output-path <path/to/schema.json>
This schema.json
file can be copied to (or the --output-path
can be set directly to) your Sanity app's repository. Your application itself does not need the full sanity
package as a dependency to use the generated types; it only needs the schema.json
file for the typegen generate
step.
If your Studio project defines multiple workspaces or you need types for different schemas (e.g., for different datasets), run the extract
command for each one, outputting to separate JSON files. For example, you could configure you Studio’s package.json
as follows:
{
"scripts": {
"schema:extract:test": "sanity schema extract --workspace test --output-path ../my-frontend-app/schema-test.json",
"schema:extract:prod": "sanity schema extract --workspace production --output-path ../my-frontend-app/schema-prod.json",
"schema:extract": "npm run schema:extract:test && npm run schema:extract:prod"
}
}
We plan to improve this schema extraction process as the SDK matures to potentially reduce the dependencies and improve overall developer experience.
Install (experimental) packages
To use the Typegen features described in this guide, your SDK app needs specific experimental versions of @sanity/cli
and groq
installed. Install these packages from within your SDK app directory:
npm install groq@typegen-experimental-2025-04-23
npm install @sanity/cli@typegen-experimental-2025-04-23 --save-dev
Package names and installation
These are experimental pre-release versions. The package names and installation process may change as these features stabilize.
Configure TypeGen (optional)
For the most common use case – a single Sanity schema for your project – no configuration file is needed. However, you'll need to create a TypeGen configuration file for more complex use cases, such as:
- Using multiple schemas (e.g., from different workspaces or for different datasets).
- Needing to explicitly map a single schema to a specific
schemaId
for accurate schema scoping (instead of using the default'default'
). - Using a different name or location for your schema file(s).
- Specifying a custom output path for the generated types file.
If you need this level of configuration, create a TypeGen configuration file (sanity-typegen.json
) at the root of your SDK app and use the unstable_schemas
array:
// sanity-typegen.json
{
"unstable_schemas": [
{
// Path to the schema
"schemaPath": "./schemas/products-schema.json",
// The schema ID, formatted as `projectId.datasetName`
"schemaId": "your-project-id.products"
},
{
"schemaPath": "./schemas/authors-schema.json",
"schemaId": "your-project-id.authors"
}
// Add more schema objects if needed
],
"overloadClientMethods": false // client methods are not needed for the App SDK
// Optional: Specify output path for generated types
// "outputPath": "./src/generated/sanity-types.ts"
}
Objects in the unstable_schemas
array each consist of the following properties:
schemaPath
: The path (relative to the project root) to the corresponding extracted schema JSON file.schemaId
: A string combining yourprojectId
anddataset
(e.g.,"your-project-id.your-dataset-name"
). This is used to map the schema to the correct project and dataset context for type generation, as the extractedschema.json
doesn't contain this information itself.
The optional outputPath
property specifies where to write the generated sanity.types.ts
file. It defaults to the project root.
By default, TypeGen works seamlessly for the common single-schema setup without extra configuration. Use sanity-typegen.json
only when your needs require more explicit control.
Generate types
With the necessary packages installed and your schema(s) extracted (and optionally configured in sanity-typegen.json
), you can run the sanity typegen generate
command from within your SDK app directory:
# use `@sanity/cli` package directly for now
./node_modules/@sanity/cli/bin/sanity typegen generate
This command reads your configuration (either sanity-typegen.json
or the default schema.json
), processes the specified schemas, and generates a sanity.types.ts
file, which contains your types. It's recommended to add this command to your SDK app’s package.json
scripts. For example:
{
"scripts": {
"typegen": "./node_modules/@sanity/cli/bin/sanity typegen generate"
}
}
Congratulations! You’ve now generated types for your schema documents, projections, and query results. With your sanity.types.ts
file in place, the App SDK hooks will automatically pick up these types.
Next, we’ll cover how to make use of these generated types in your SDK app.
Use the generated types
TypeGen generates interfaces for each document type defined in your schemas. For projects using multiple schemas/datasets defined in sanity-typegen.json
, it utilizes a helper type SchemaOrigin
(imported from groq
) to brand the types.
This allows TypeScript to narrow down the possible document types based on the dataset context provided via a DocumentHandle
. See the code below for an example of this:
import {useDocument, createDocumentHandle} from '@sanity/sdk-react'
// Assuming 'book' is only in 'test' dataset, 'dog' only in 'production'
const testHandle = createDocumentHandle({
projectId: 'your-project-id',
dataset: 'test',
documentId: 'some-id',
documentType: 'book', // Type narrowed to 'book'
})
const prodHandle = createDocumentHandle({
projectId: 'your-project-id',
dataset: 'production',
documentId: 'another-id',
documentType: 'dog', // Type narrowed to 'dog'
})
function MyComponent() {
const {data: bookData} = useDocument(testHandle)
// bookData is correctly typed as Book
const {data: dogData} = useDocument(prodHandle)
// dogData is correctly typed as Dog
// ...
}
Handles and literal types
For TypeGen to correctly infer types in hooks like useDocument
, it needs to know the specific literal type of the documentType
(e.g., 'book'
instead of just string
).
The App SDK provides helper functions (like createDocumentHandle
and createDatasetHandle
) that help capture these literal types:
import {createDocumentHandle} from '@sanity/sdk'
// Using the helper ensures handle.documentType is typed as 'book'
const handle = createDocumentHandle({
documentId: '123',
documentType: 'book',
dataset: 'production',
projectId: 'abc',
})
Alternatively, if you prefer defining handles as plain objects, use as const
to ensure the documentType
has the literal type of 'book'
:
const handle = {
documentId: '123',
documentType: 'book',
dataset: 'production',
projectId: 'abc',
} as const // 'as const' ensures documentType is 'book', not string
We recommend that you use createDocumentHandle
(or other create*Handle
helpers) when using Typegen for cleaner code.
Document projections
To get types for GROQ projections used with useDocumentProjection
and TypeGen, you must define them using the defineProjection
helper from groq
.
import {defineProjection} from 'groq'
import {useDocumentProjection, type DocumentHandle} from '@sanity/sdk-react'
// Typegen derives the type name (AuthorSummaryProjectionResult) from the variable name
export const authorSummary = defineProjection(`{
"name": name,
"favoriteBookTitles": favoriteBooks[]->title,
}`)
function AuthorDetails({doc}: {doc: DocumentHandle<'author'>}) {
// The type of `data` is inferred from `authorProjection`
const {data} = useDocumentProjection({
...doc, // Spread the handle containing documentId, type, etc.
projection: authorProjection,
})
// data is typed as AuthorSummaryProjectionResult
// Autocompletion works for data.name and data.favoriteBookTitles
return <div>{data.name}</div>
}
There are few important things to note here:
- In the example above, the generated type (e.g.,
AuthorSummaryProjectionResult
) includes aProjectionBase
brand, allowing unions of projection results if a projection applies to multiple document types. - TypeGen intelligently removes types from the projection result if all fields in the projection evaluate to
null
for a given document type. - When using Typegen, you cannot pass raw projection strings to
useDocumentProjection
and get type inference; you must usedefineProjection
.
GROQ queries
Similarly to useProjection
, when using the useQuery
hook, you must define your GROQ queries using defineQuery
from the groq
package to get type inference:
import {defineQuery} from 'groq'
import {useQuery} from '@sanity/sdk-react'
// Typegen derives the type name (AllBooksQuery) from the variable name
export const allBooksQuery = defineQuery('*[_type == "book"]{ _id, title }')
function BookList() {
// Type of `data` is inferred from `allBooksQuery`
const {data} = useQuery({query: allBooksQuery})
// data is typed as Array<{_id: string, title: string}> (or similar)
return (
<ul>
{data.map((book) => (
<li key={book._id}>{book.title}</li>
))}
</ul>
)
}
Note that useQuery
accepts options as a single object, allowing you to spread handles easily. For example:
const handle = createDatasetHandle({dataset: 'test', projectId: 'abc'})
const {data} = useQuery({...handle, query: allBooksQuery})
Document lists
The App SDK’s document list hooks, useDocuments
and usePaginatedDocuments
, benefit from TypeGen through dataset scoping (as shown earlier). You can use the documentType
option to specify the document type(s) you are querying:
import {usePaginatedDocuments, createDatasetHandle} from '@sanity/sdk-react'
import {DocumentPreview} from './your-document-preview'
const testDataset = createDatasetHandle({dataset: 'test', projectId: 'abc'})
function MixedList() {
// Specify the types being queried
const {data} = usePaginatedDocuments({
...testDataset,
documentType: ['author', 'book'], // Pass string or array of strings
})
// `data` is an array of DocumentHandles, correctly scoped.
// If used with `useDocument` (and other hooks) later, types will be scoped
// appropriately (e.g. Author | Book).
return (
<ul>
{data.map((doc) => (
<Suspense key={doc.documentId} fallback={<li>Loading...</li>}>
<DocumentPreview doc={doc} />
</Suspense>
))}
</ul>
)
}
Specific document types
When you know the specific document type you're dealing with, you can make your TypeScript code even more precise using the methods described below.
Parameterizing DocumentHandles
DocumentHandle
is a generic type that accept type parameters. You can provide a specific document type literal (like 'book'
) as a type argument. This is useful for typing props or variables that should only reference a handle for a specific document type:
import {type DocumentHandle} from '@sanity/sdk-react'
// This function expects a handle that *must* reference a 'book' document
function BookComponent({doc}: {doc: DocumentHandle<'book'>}) {
// Thanks to DocumentHandle<'book'>, TypeScript knows the context
const {data} = useDocument(doc)
// `data` will be typed as the generated `Book` interface
// ...
}
This works because the full definition of DocumentHandle
includes generic type parameters (TDocumentType
, TDataset
, TProjectId
) that default to string
but can be made more specific.
Using SanityDocument
for document data
If you need the type for the actual document data itself (not just the handle), the groq
package exports the SanityDocument<TDocumentType>
helper type. Pass the document type literal to get the corresponding generated interface for the document content:
import {type SanityDocument} from 'groq'
type BookData = SanityDocument<'book'>
// BookData is now equivalent to the generated Book interface (e.g., { _id: string; title: string; ... })
// This function expects the fully typed book data
function processBook(book: BookData) {
console.log(book.title) // Autocomplete works!
}
In summary:
- Use
DocumentHandle<'yourType'>
to constrain a document handle to documents of a specific type. - Use
SanityDocument<'yourType'>
to type the actual data structure of a document of a specific type.
Workflow considerations
Regeneration
You'll need to re-run npm run typegen
whenever you:
- Change your Sanity schemas.
- Add or modify queries/projections defined with
defineQuery
ordefineProjection
. - Consider integrating this into your
dev
script or a file watcher.
TypeGen is additive
TypeGen is designed to enhance the App SDK experience. If you don't use it, the App SDK hooks will still work, but data types will often default to any
or unknown
, losing the benefits of TypeScript. Adopting TypeGen later should be a non-breaking change that simply adds type safety.
JavaScript projects
Even if your project doesn't use TypeScript, you can still leverage TypeGen to enhance your JavaScript development experience.
By following the steps in this guide – extracting your schema, installing the necessary packages, using helpers like createDocumentHandle
, defineProjection
, and defineQuery
, and running npm run typegen
– you create a sanity.types.ts
file.
While your JavaScript code won't undergo compile-time type checking, modern code editors (like VS Code) that use the TypeScript language service can read this generated file.
This often results in significantly better autocompletion within your JavaScript files when interacting with App SDK hooks and data. Remember, however, that using defineProjection
and defineQuery
is still required for TypeGen to generate types for those specific artifacts.
Was this page helpful?