Sanity Studio v5.0.0: React 19.2 as baseline, TypeGen improvements

Sanity Studio v5 requires React 19.2 and includes breaking changes to TypeGen output in the Sanity CLI. Your schemas, plugins, and Studio customizations work exactly as before.

React 19.2 is now required for Sanity Studio

Sanity Studio v5 requires React 19.2.2 or higher (both react and react-dom).

Already on React 19.2?

Upgrade Sanity and you're done:

npm install sanity@latest

Still on React 18?

Before upgrading: Run sanity dev with React 18.3 and check for deprecation warnings. Fix any that appear before moving to React 19. You can also enable React Strict Mode in your sanity.cli.ts to catch potential compatibility issues:

export default defineCliConfig({
reactStrictMode: true,
// ...
})

Or via environment variable: SANITY_STUDIO_REACT_STRICT_MODE="true"

Then upgrade your dependencies:

npm install react@latest react-dom@latest
npm install sanity@latest

Why we're doing this: React 19 shipped a year ago with significant performance improvements. Maintaining React 18 compatibility held us back from using features like the use() hook and <Activity> component, and added overhead to every feature we shipped. The ecosystem has moved on (Next.js 15+ requires React 19), and so are we.

For the full story on why we're making this change, notes on auto-updating Studios, and information about the recent React security advisory, read our announcement post.

Notes for Studio plugin developers

If your plugin only supports React 18: Update it to support 19.2 and ship a new version. Otherwise, your plugin won't work with Sanity v5 studios.

If your plugin already works with React 19: Update your peerDependencies to include Sanity v5:

"peerDependencies": {
"sanity": "^4.0.0 || ^5.0.0-0"
}

The trailing -0 makes your plugin compatible with v5 prereleases during testing.

TypeGen improvements for Sanity CLI

We're preparing TypeGen for general availability. This release adds custom GROQ function support and fixes how type names are cased for snake_case queries. That casing fix is a breaking change, if you use snake_case query names, you'll need to update your type imports after regenerating.

Custom GROQ function support

If you've been avoiding using custom GROQ functions because of lacking TypeGen support, then that time is over. The only workaround was to add a // @sanity-typegen-ignore comment and write types manually. Now it just works™.

export const carsWithPriceInfoQuery = defineQuery(`
fn car::priceInfo($vehicle) = $vehicle {
price,
"priceCategory": select(
price < 50000 => "budget",
price < 150000 => "mid-range",
price >= 150000 => "premium"
)
};

*[_type == "vintageCar"] | order(price desc) {
_id,
make,
model,
...car::priceInfo(@)
}
`);

export type CarsWithPriceCategoryQueryResult = Array<{
_id: string;
make: string;
model: string;
price: number;
priceCategory: "budget" | "mid-range" | "premium";
}>;

TypeGen progress indicator

sanity typegen generate now shows a progress indicator with percentage, so you're not left wondering if it's working on large schemas.

Unknown block type "muxVideo", specify a component for it in the `components.types` option

TypeGen preserves query_name_casing (Breaking change)

TypeGen now appends the result suffix in a way that matches your query's casing style. This primarily affects snake_case query names:

Unknown block type "tableBlock", specify a component for it in the `components.types` option

camelCase and PascalCase query names (pageQuery, PageQuery) are unaffected.

If you import generated types by name, update those references after regenerating.

Improved hoisting of schema types

The schema extraction now detects object types used multiple times in your schema more effectively and hoists them in the schema.json and the generated types, to reduce code duplication and improve readability.

It also generates types for document type references to avoid repeated object structures in the schema output.

import {defineField, defineType} from 'sanity'

// A reusable SEO object used in multiple documents
const seoFields = defineField({
name: 'seo',
type: 'object',
fields: [
{name: 'title', type: 'string'},
{name: 'description', type: 'string'},
],
})

export const author = defineType({
name: 'author',
type: 'document',
fields: [{name: 'name', type: 'string'}],
})

export const post = defineType({
name: 'post',
type: 'document',
fields: [
{name: 'title', type: 'string'},

// references to docs → reference hoisting
{name: 'author', type: 'reference', to: [{type: 'author'}]},
{name: 'reviewer', type: 'reference', to: [{type: 'author'}]},

// SEO object used here → inline hoisting when also used in `page`
seoFields,
],
})

export const page = defineType({
name: 'page',
type: 'document',
fields: [
{name: 'title', type: 'string'},

// reference to author → shares hoisted doc reference type
{name: 'author', type: 'reference', to: [{type: 'author'}]},

// Same SEO object → triggers inline hoisting
seoFields,
],
})

export const schemaTypes = [author, post, page]

export type Page = {
_id: string
_type: 'page'
_createdAt: string
_updatedAt: string
_rev: string
title?: string
author?: {
_ref: string
_type: 'reference'
_weak?: boolean
[internalGroqTypeReferenceTo]?: 'author'
}
seo?: {
title?: string
description?: string
}
}

export type Post = {
_id: string
_type: 'post'
_createdAt: string
_updatedAt: string
_rev: string
title?: string
author?: {
_ref: string
_type: 'reference'
_weak?: boolean
[internalGroqTypeReferenceTo]?: 'author'
}
reviewer?: {
_ref: string
_type: 'reference'
_weak?: boolean
[internalGroqTypeReferenceTo]?: 'author'
}
seo?: {
title?: string
description?: string
}
}

// ...

export type Seo = {
title?: string
description?: string
}

export type AuthorReference = {
_ref: string
_type: 'reference'
_weak?: boolean
[internalGroqTypeReferenceTo]?: 'author'
}

export type Page = {
_id: string
_type: 'page'
_createdAt: string
_updatedAt: string
_rev: string
title?: string
author?: AuthorReference
seo?: Seo
}

export type Post = {
_id: string
_type: 'post'
_createdAt: string
_updatedAt: string
_rev: string
title?: string
author?: AuthorReference
reviewer?: AuthorReference
seo?: Seo
}

// ...

Simplified Programmatic API for TypeGenerator

The TypeGenerator class from @sanity/codegen has a simplified API. The generateTypes method now returns the generated code directly and uses an optional reporter callback for progress events.

This affects libraries and other community tools that integrate with TypeGen programmatically. If you maintain such tooling, check the updated API surface.

Smart typography for the Portable Text Editor is enabled by default

Sanity Studio v4.22.0 introduced the typography plugin for the Portable Text Editor, but with the smart typography disabled by default. We realized that this behavior is what most users want and expect, so we flipped the switch. You can still disable it globally or on a per-field basis by adding the configuration documented here.