> For AI agents: the complete Sanity documentation index is available at [https://www.sanity.io/docs/llms.txt](https://www.sanity.io/docs/llms.txt).

# App SDK and TypeGen

Learn how to use Sanity TypeGen with the App SDK for increased type safety and improved developer experience.

> [!WARNING]
> The experimental TypeGen integration described on this page is deprecated. It may conflict with modern Sanity CLI versions. For new projects, use the current, non-experimental TypeGen instead. See [Migrating from experimental TypeGen with the App SDK](https://www.sanity.io/docs/app-sdk/migrating-from-experimental-typegen-in-app-sdk).

> [!NOTE]
> This page is for using TypeGen with the App SDK. See [the TypeGen article](https://www.sanity.io/docs/apis-and-sdks/sanity-typegen) to generate types for your Studio and front end applications.

[Sanity TypeGen](https://www.sanity.io/docs/apis-and-sdks/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.

This guide shows you how to set up and use TypeGen in your SDK app.

## Setup

Using TypeGen involves two main steps: extracting your schema(s) and then generating the types. Both commands are available through 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.

Use the `sanity schema extract` command within your Studio project or a project that has the `sanity` package installed:

**npm**

```shell
npx sanity schema extract --workspace <workspace-name> --path <path/to/schema.json>
```

**pnpm**

```shell
pnpm dlx sanity schema extract --workspace <workspace-name> --path <path/to/schema.json>
```

**yarn**

```shell
yarn dlx sanity schema extract --workspace <workspace-name> --path <path/to/schema.json>
```

**bun**

```shell
bunx sanity schema extract --workspace <workspace-name> --path <path/to/schema.json>
```

This `schema.json` file can be copied to (or the `--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 (for example, for different datasets), run the `extract` command for each one, outputting to separate JSON files. For example, you could configure your studio’s `package.json` as follows:

**package.json**

```json
{
  "scripts": {
    "schema:extract:test": "sanity schema extract --workspace test --path ../my-frontend-app/schema-test.json",
    "schema:extract:prod": "sanity schema extract --workspace production --path ../my-frontend-app/schema-prod.json",
    "schema:extract": "npm run schema:extract:test && npm run schema:extract:prod"
  }
}
```

### 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**

```shell
npm install --save-exact groq@typegen-experimental-2025-04-23
npm install --save-dev --save-exact @sanity/cli@typegen-experimental-2025-04-23
```

**pnpm**

```shell
pnpm add --save-exact groq@typegen-experimental-2025-04-23
pnpm add --save-dev --save-exact @sanity/cli@typegen-experimental-2025-04-23
```

**yarn**

```shell
yarn add --exact groq@typegen-experimental-2025-04-23
yarn add --dev --exact @sanity/cli@typegen-experimental-2025-04-23
```

**bun**

```shell
bun add --exact groq@typegen-experimental-2025-04-23
bun add --dev --exact @sanity/cli@typegen-experimental-2025-04-23
```

The `--save-exact` flag pins these versions, so a fresh install can't resolve them to stable releases that don't support this workflow. The App SDK also depends on a newer release of `groq`. Without an override, npm installs a second copy of `groq` for the SDK, and hook results are typed as `never` (for example, `Property 'title' does not exist on type 'never'`). To make the SDK use the experimental `groq`, add an `overrides` entry to your SDK app's `package.json`:

**package.json**

```json
{
  "overrides": {
    "groq": "$groq"
  }
}
```

Then delete `node_modules` and `package-lock.json` and reinstall. A plain `npm install` leaves the SDK's existing copy of `groq` in place:

**npm**

```shell
rm -rf node_modules package-lock.json
npm install
```

**pnpm**

```shell
rm -rf node_modules package-lock.json
pnpm install
```

**yarn**

```shell
rm -rf node_modules package-lock.json
yarn install
```

**bun**

```shell
rm -rf node_modules package-lock.json
bun install
```

### 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 (for example, 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**

```json
// 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
  // "generates": "./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 your `projectId` and `dataset` (for example, `"YOUR_PROJECT_ID.YOUR_DATASET"`). This is used to map the schema to the correct project and dataset context for type generation, as the extracted `schema.json` doesn't contain this information itself.

The optional **generates** 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:

**Terminal**

```sh
# 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. Add this command to your SDK app’s `package.json` scripts. For example:

**package.json**

```json
{
  "scripts": {
    "typegen": "./node_modules/@sanity/cli/bin/sanity typegen generate"
  }
}
```

You've now generated types for your schema documents and query results. With your `sanity.types.ts` file in place, the App SDK hooks will automatically pick up these types.

## 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 uses 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 by a `DocumentHandle`. The following example shows this:

**Document type narrowing**

```tsx
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` (for example, `'book'` instead of `string`).

The App SDK provides helper functions (like `createDocumentHandle` and `createDatasetHandle`) that help capture these literal types:

**Document handle creation**

```typescript
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'`:

**Document handle creation with object literals**

```typescript
const handle = {
  documentId: '123',
  documentType: 'book',
  dataset: 'production',
  projectId: 'abc',
} as const // 'as const' ensures documentType is 'book', not string
```

> [!TIP]
> We recommend that you use `createDocumentHandle` (or other `create*Handle` helpers) when using TypeGen for cleaner code.

### GROQ queries

When using the `useQuery` hook, you **must** define your GROQ queries using `defineQuery` from the `groq` package to get type inference:

**Defining queries**

```tsx
import {defineQuery} from 'groq'
import {useQuery} from '@sanity/sdk-react'

// TypeGen derives the type name (AllBooksQueryResult) 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:

**Spreading options to `useQuery`**

```typescript
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. You can use the `documentType` option to specify the document type(s) you are querying:

**Paginated list example**

```tsx
import {Suspense} from 'react'
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 following methods.

#### Type a handle with `DocumentHandle<'book'>`

`DocumentHandle` is a generic type that accepts 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:

**Parameterizing a Document Handle**

```tsx
import {type DocumentHandle, useDocument} 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.

#### Type document data with `SanityDocument`

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:

**Parameterizing SanityDocument**

```typescript
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 defined with `defineQuery`.
- 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 adds type safety.

### JavaScript projects

Even if your project doesn't use TypeScript, you can still use 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` 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 `defineQuery` is still required for TypeGen to generate types for those specific artifacts.



## Related changelog entries

Entries are listed newest first; each link points to the full entry as markdown. Follow one when you need to know what changed, when, or why — for example, to summarize recent updates, explain behavior that differs from older documentation, or check whether a fix has shipped.

- [Sanity React App SDK v3.9.0: Per-dataset TypeGen (beta) and clearer project access errors](https://www.sanity.io/docs/changelog/sdk-react-My45LjA.md) — October 13, 2026
- [Sanity React App SDK v3.2.0: Simplified GROQ type generation](https://www.sanity.io/docs/changelog/23c31ecb-c938-4f66-8c26-5641ffbbb0b9.md) — September 15, 2026