Per-dataset TypeGen (beta) and clearer project access errors

Published: October 13, 2026

v3.9.0

SDK hooks can now infer document, field, query, and projection types for each dataset your app reads, from types generated with typegen.resources in @sanity/cli 8.16.0. This release also explains the error users see when they sign in with an account that has no access to a project, and returns projectId and dataset as required from createDocumentHandle. Most apps need no code changes. Apps that store a created handle in state, a variable, or a context with an inferred type may need a type annotation, described below.

Types for each dataset with TypeGen (beta)

With @sanity/cli 8.16.0 or later, list each dataset your app reads under typegen.resources in sanity.cli.ts, then run sanity typegen generate. TypeGen writes one types file per dataset and registers its types under that dataset's project ID and dataset. Hooks such as useDocument, useQuery, and useDocumentProjection look up types by the project ID and dataset on the handle, so the same document type or query can have different types in different datasets. useDocument with a path returns that field's type for the dataset.

defineProjection now takes the document type as an optional first argument, as in defineProjection('book', '{title}'). TypeGen then generates the projection's type for that document type only, instead of for every document type in the schema. The function still returns the projection string unchanged.

When TypeScript only knows a handle's dataset or document type as string, such as a prop typed DocumentHandle<'book'> or a handle from useDocuments without a literal documentType, hooks return the union of the matching types across your generated datasets and document types.

This workflow is in beta. typegen.resources, the SDK's type resolvers, and defineProjection with a document type can change in minor releases. sanity typegen generate --watch and generation during sanity dev don't support typegen.resources yet. Types saved from the experimental TypeGen fork keep working for datasets without a new registration. The TypeScript with TypeGen guide covers setup, which dataset's types a hook uses, and migrating from experimental TypeGen.

Clearer errors when a user signs in with the wrong account

Sanity creates a separate account for each sign-in method, even for the same email address. When a user signs in with an account that isn't a member of the project, the API rejects the request with a 401 projectUserNotFoundError. useQuery, useDocuments, and usePaginatedDocuments now throw this error with a message that names the sign-in method, such as Google or SSO, and tells the user to sign in with the account that has access. Error boundaries that render error.message show this message instead of project user not found for user ID .... The error is still a ClientError with the original status code and response body.

When no error boundary in your app catches the error, the SDK's error screen shows the signed-in account, explains that each sign-in method is a separate account, and lists IDs for support. In standalone apps that don't use a static token, it also offers to sign out and switch accounts.

Call getProjectAccessErrorProjectId(error) in your error boundary to read the ID of the project that rejected the request. Rethrow the error when your app can't work without that project, so the SDK's screen replaces the app. Handle it in place when only part of your app reads from that project:

useDocument and the projection hooks don't use the new message yet. useDocument reports a failed connection for this case, and projection hooks log fetch errors instead of throwing them.

Handle creators return the project ID and dataset as required

createDocumentHandle, createDocumentTypeHandle, and createDatasetHandle in @sanity/sdk now return projectId and dataset as required when you pass both as strings. createDocumentHandle({projectId: 'abc123', dataset: 'production', ...}).projectId is typed 'abc123' instead of 'abc123' | undefined. Handles that omit either field return the same type as before.

A useState value, variable, or React context whose type is inferred from one of these handles now requires both fields. It no longer accepts a handle whose projectId and dataset are optional, such as a DocumentHandle prop or an item from useDocuments, and you can't set either field to undefined. Annotate the type with the document type, dataset, and project ID:

🐛 Notable bugfixes and improvements

  • In the Dashboard, apps configured with resources instead of projectId now show the access request prompt for the project that rejected a request, instead of an authentication error.
  • useNavigate from @sanity/sdk-react/dashboard no longer requires a callback, so an app without its own router can call useNavigate(); an app with a router still passes one so the router follows the Dashboard's back and forward navigation.
  • Editor hovers and type errors for document hooks show a TypeGen document type by its own name, such as Movie, instead of DocumentRecord<Movie>.
Loading...