Sanity SDK

Framework-agnostic TypeScript core for building custom apps on top of Sanity's real-time content platform.

SDK
npm
v3.1.0
17stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
70/100Good
Development Activity100
Maintenance100
Community24
Maturity44
Momentum12

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
81/100Excellent
Architecture87
Code Quality88
Innovation82
Learning Curve65

@sanity/sdk is the framework-agnostic core of the Sanity App SDK, a toolkit for building fully custom applications that read and write Sanity content. It packages the business logic behind authentication, document CRUD and lifecycle actions, GROQ-backed queries, presence, comments, releases, and permissions into a single reactive store, so any UI layer can subscribe to live Sanity data without hand-rolling its own sync layer.

Where Sanity Studio bundles this logic into one editing surface, the SDK exposes it as composable functions — getDocumentState, getQueryState, applyDocumentActions, getClientState, and dozens more — each returning a StateSource with synchronous getCurrent(), imperative subscribe(), and RxJS observable access patterns. @sanity/sdk-react builds React hooks on top of this exact core, and the package’s /agent and /comlink sub-entries expose AI-agent actions and cross-window messaging utilities for building embedded dashboard apps.

It targets teams building bespoke internal tools, embedded workflow apps, or dashboard-hosted integrations against Sanity’s Content Lake, where the default Studio UI doesn’t fit but the underlying real-time data model still should.

What You Get

  • Instance-scoped stores - createSanityInstance plus bindActionByResource/bindActionGlobally isolate state per project/dataset (or share it globally) with automatic disposal-driven cleanup.
  • Document lifecycle actions - createDocument, editDocument, publishDocument, discardDocument, and release actions (createRelease, scheduleRelease) applied transactionally via applyDocumentActions.
  • Reactive query and document state - getQueryState/resolveQuery and getDocumentState/resolveDocument return StateSource objects built for React’s useSyncExternalStore, backed by GROQ and live content sync.
  • Auth and permissions built in - getAuthState, setAuthToken, handleAuthCallback, and getPermissionsState/checkPermissions cover login flows and per-action access checks without extra wiring.
  • Presence and comments - getPresence/reportPresence and the comment-thread APIs (createComment, resolveCommentThreads) bring collaborative-editing primitives out of Studio and into custom apps.
  • AI agent and cross-window sub-entries - @sanity/sdk/agent exposes agentGenerate/agentPatch/agentPrompt for AI-assisted content actions, and @sanity/sdk/comlink provides typed channel/controller utilities for dashboard-embedded apps.

Common Use Cases

  • Custom internal editing tools - a content team builds a purpose-specific editor for one document type instead of exposing the full Studio.
  • Dashboard-embedded apps - an app installed into Sanity Dashboard uses comlink to communicate with its host frame while reading/writing content through the core store.
  • Non-React integrations - a team building a Vue, Svelte, or vanilla-JS surface uses the framework-agnostic core directly rather than @sanity/sdk-react.
  • AI-assisted content workflows - a product uses the /agent entry point to let an LLM draft or patch Sanity documents through agentGenerate/agentPatch with the same permission and document-lifecycle guarantees as manual edits.
  • Real-time collaborative review surfaces - an app layers presence and comment threads on top of live document state to mirror Studio’s collaborative editing inside a custom UI.

Under The Hood

Architecture The SDK is organized as independently-scoped “stores” (auth, client, document, query, presence, users, releases, comments, etc.), each defined via defineStore and instantiated per key through createStoreInstance. createActionBinder is the load-bearing abstraction: bindActionByResource, bindActionByResourceAndPerspective, and bindActionGlobally all derive from one generic binder that keys store instances by project/dataset/perspective (or globally, for stores like auth), registers each consuming SanityInstance for disposal tracking, and tears the underlying store down once the last instance unsubscribes. Every store action ultimately reads/writes through this same binder, so document, query, and presence state all share one lifecycle and disposal model despite being logically separate stores. createSanityInstance itself is a lightweight, disposable handle carrying config and a listener registry, not a heavyweight client object.

Tech Stack Written in strict TypeScript against Node/browser targets, using Zustand’s vanilla createStore (with devtools middleware) for the underlying mutable state container and RxJS for the reactive surface layered on top via createStoreState’s observable. @sanity/client provides the HTTP/CDN transport (wrapped by clientStore, which reconfigures clients automatically on token or auth-method changes), while groq/groq-js power query parsing and reselect-style selector patterns drive derived state. The monorepo is Turborepo/pnpm-workspace managed, built per-package with @sanity/pkg-utils and Vite, and ships multiple explicit sub-entry points (., /agent, /comlink, /_internal) rather than one flat export surface.

Code Quality Test coverage is extensive and consistent: nearly every source module in packages/core/src (auth, document, query, presence, releases, users, utils, etc.) ships a co-located .test.ts, with additional .test-d.ts type-level tests for public API shapes and dedicated concurrency tests for the document store. The TypeScript config extends a shared strictest preset (strictNullChecks, noImplicitAny, noImplicitReturns, noImplicitOverride, and more all enabled), ESLint and a custom formatter (oxfmt) run repo-wide, and GitHub Actions runs dedicated lint, type-check, test, and end-to-end-test workflows on every change. JSDoc comments consistently tag exports @public or @internal, which doubles as the contract enforced by the package’s generated API surface.

What Makes It Unique The core abstraction — a StateSource exposing synchronous getCurrent(), imperative subscribe(), and an RxJS observable from the same underlying value — is deliberately designed so React’s useSyncExternalStore and manual/reactive consumers can share one implementation with no duplicated sync logic. Combined with the generic action-binder pattern that scopes any store to a resource, a resource+perspective, or globally, the SDK lets one store definition serve isolated per-dataset state and shared global state (like auth) through the same mechanism, rather than hand-writing separate caching and lifecycle code for each concern.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search