
Grab your gear: The official Sanity swag store
Read Grab your gear: The official Sanity swag storeTypeGen generates exact TypeScript types from your schemas and queries. So AI tools stop guessing and developers stop debugging phantom fields.

Kristoffer Brabrand
Senior Software Engineer

Sindre Gulseth
Staff Software Engineer

Knut Melvær
Principal Developer Marketing Manager
Published
AI coding tools are only as good as the context you give them. Point Claude, Cursor, or Copilot at vague, or no, types and you get vague suggestions. Give them precise types and they autocomplete correctly, catch edge cases, and stop hallucinating field names.
Sanity TypeGen generates precise TypeScript types from your content schemas and GROQ queries. Which means that you (and your agents) get the most out of the flexbility of GROQ, without losing the power of a well-defined schema. Few, if any, CMSes out there give you this developer experience.
So instead of event.format: string, you get 'in-person' | 'virtual'. Instead of post.coverImage: any, you get the full image structure with asset reference and optional alt text. Your editor autocompletes GROQ projections. Your AI assistant stops inventing fields (🤞). Your types stay in sync with your content model.
TypeGen is now generally available in Sanity Studio v5 with automatic regeneration, consolidated configuration, and stable APIs. If you used the beta, the migration is straightforward, the breaking changes are intentional improvements that make generated types more accurate.
The most requested feature during the beta was automation. Manually running sanity schema extract and sanity typegen generate every time you changed a schema or query worked, but it was easy to forget and end up with stale types.
Now you can turn on automatic type generation with a single config option. When you enable typegen.enabled: true, TypeGen will run automatically during sanity dev:
// sanity.cli.ts
import { defineCliConfig } from 'sanity/cli'
export default defineCliConfig({
// …your project config
typegen: {
enabled: true,
},
})Running sanity dev now watches your schema and query files automatically. If you're running a separate frontend repo, use the --watch flag:
sanity typegen generate --watch
TypeGen originally had sanity-typegen.json for configuration. Now everything lives in sanity.cli.ts alongside your other CLI settings.
// sanity.cli.ts
import { defineCliConfig } from 'sanity/cli'
export default defineCliConfig({
// …your project config
typegen: {
enabled: true,
path: './sanity.types.ts',
schema: './schema.json',
generates: {
'./src/types.ts': {
// custom generation options
},
},
overloadClientMethods: true,
},
})The old config file still works but now shows a deprecation warning.
One caveat: if you’re in a mono repo where the studio and other apps are in different packages, you'll still need the JSON schema for those:
Enable schemaExtraction.enabled: true in the studio's sanity.cli.ts to produce the schema.json.
Then point your frontend/apps repo's typegen config at that generated schema file.
We rewrote the type generation internals with memoization, so updates in watch mode are significantly faster. You'll also see a progress indicator during generation so you know it's working. (No, we can’t promise we have finaly solved the halting problem.)
The generated types are now more accurate and easier to work with:
MyPageQuery now produces MyPageQueryResult instead of MypagequeryResult// Before
type Post = {
my-field: string // ❌ Invalid TypeScript
}
// After
type Post = {
'my-field': string // ✅ Properly quoted
}ArrayOf utility type, reducing duplication and file sizeTwo new utility types make it easier to work with complex content models. This is especially handy for landing page-type content that tends to get nested.
The Get utility extracts deeply nested properties (up to 20 levels):
import type { Get } from '@sanity/codegen'
import type { Page } from './sanity.types'
// Extract a deeply nested type
type HeroSection = Get<Page, 'sections', number, 'hero'>
// No more juggling NonNullable and index access
type OldWay = NonNullable<NonNullable<NonNullable<Page>['sections']>[number]>['hero']The FilterByType utility pulls specific types from unions using the _type discriminator:
import type { FilterByType } from '@sanity/codegen'
import type { PageBuilder } from './sanity.types'
// Extract only hero blocks from a union of block types
type HeroBlock = FilterByType<PageBuilder, 'hero'>
// Works with multiple types too
type ContentBlocks = FilterByType<PageBuilder, 'hero' | 'textBlock' | 'imageGallery'>The TypeGen tooling now supports @sanity/sveltekit defineQuery imports and can parse .svelte and .vue files. Support for .astro files was already available.
General availability means the TypeGen APIs are stable. The configuration format, CLI commands, and utility types won't change in breaking ways. Generated output will continue to improve, but those improvements will remain compatible with your existing code.
Some limitations remain:
unknownSetup depends on your project structure. Here are the three most common scenarios:
In your studio, you typically want schema extraction enabled to generate the schema.json file. If you only need the schema.json (for example, to use in other packages), enable schemaExtraction.enabled: true. If you also need types in your studio code (for custom components, etc.), add typegen.enabled: true as well. Both control whether these processes run as part of sanity dev and sanity build commands.
// sanity.cli.ts
import { defineCliConfig } from 'sanity/cli'
export default defineCliConfig({
// …your project config
schemaExtraction: {
enabled: true,
},
typegen: {
enabled: true,
},
})In your studio's sanity.cli.ts, enable schemaExtraction.enabled: true to generate the schema.json file when you run sanity dev or sanity build. Add typegen.enabled: true if you also want to generate types for use in the studio itself.
In your frontend packages, run sanity typegen generate --watch and configure the path to point to the schema.json generated by the studio. Since frontend repos don't use sanity dev or sanity build, you run the typegen command directly with the --watch flag for automatic regeneration during development.
If your Studio is embedded on an app route (Next.js, Astro, Nuxt, etc.), you're not using sanity dev or sanity build to build your app. This means the enabled flags won't help you. Instead, you'll need to run the CLI commands directly:
sanity schema extract to generate the schema JSONsanity typegen generate to create types (use --watch for automatic regeneration during development)If you're using the beta, here's the migration path:
sanity-typegen.json to sanity.cli.tsenabled: trueThe breaking changes are intentional corrections that make the generated types more accurate.
Here’s the smørgåsbord of most of the things we improved:
sanity dev and sanity buildsanity.cli.tsArrayOf utility type for smaller outputGet utility for extracting nested types (up to 20 levels)FilterByType utility for filtering union types@sanity/sveltekit defineQuery.svelte and .vue files@sanity-typegen-ignore comment supportThe Sanity Learn course has been updated to reflect all these changes. If you're new to TypeGen or want to see the latest features in action, it's a great place to start.
Thanks to Kristoffer Brabrand, Rico Kahler, and Sindre Gulseth for the work here. And to everyone who tested the beta and provided feedback. If you run into issues or have suggestions, join us in the #typescript channel in the Sanity community Discord.
Content backend


The only platform powering content operations


Tecovas strengthens their customer connections
Build and Share

Grab your gear: The official Sanity swag store
Read Grab your gear: The official Sanity swag store// sanity.cli.ts
import { defineCliConfig } from 'sanity/cli'
export default defineCliConfig({
// …your project config
typegen: {
enabled: true,
},
})sanity typegen generate --watch// sanity.cli.ts
import { defineCliConfig } from 'sanity/cli'
export default defineCliConfig({
// …your project config
typegen: {
enabled: true,
path: './sanity.types.ts',
schema: './schema.json',
generates: {
'./src/types.ts': {
// custom generation options
},
},
overloadClientMethods: true,
},
})// Before
type Post = {
my-field: string // ❌ Invalid TypeScript
}
// After
type Post = {
'my-field': string // ✅ Properly quoted
}import type { Get } from '@sanity/codegen'
import type { Page } from './sanity.types'
// Extract a deeply nested type
type HeroSection = Get<Page, 'sections', number, 'hero'>
// No more juggling NonNullable and index access
type OldWay = NonNullable<NonNullable<NonNullable<Page>['sections']>[number]>['hero']import type { FilterByType } from '@sanity/codegen'
import type { PageBuilder } from './sanity.types'
// Extract only hero blocks from a union of block types
type HeroBlock = FilterByType<PageBuilder, 'hero'>
// Works with multiple types too
type ContentBlocks = FilterByType<PageBuilder, 'hero' | 'textBlock' | 'imageGallery'>// sanity.cli.ts
import { defineCliConfig } from 'sanity/cli'
export default defineCliConfig({
// …your project config
schemaExtraction: {
enabled: true,
},
typegen: {
enabled: true,
},
})