You are invited to build and deploy ambitious content experiences faster than ever, using whatever components you like.
Simeon Griggs
Principal Educator at Sanity
Published
Over the last few years, I've shipped over a dozen plugins and written thousands of words of tutorials and lessons on how to customize Sanity Studio.
The Sanity App SDK is the toolkit I have always wanted.
What was always possible is now far simpler with the Sanity App SDK. Until now, building your own content experiences meant building them inside Sanity Studio and having to recreate the Studio's UX.
The Sanity App SDK distills the behaviors authors expect—live updating document lists, automatically creating drafts, multiplayer editing—and into reusable hooks for developers to build novel content applications rapidly.
With this UI baked in, you're free from technical overhead to build impressive content applications that solve for content creators' needs for immediate business impact. Build fit-for-purpose applications instead of trying to mutate a cookie-cutter CMS into something it was never meant to be.
The App SDK welcomes developers to an ambitious future that is less about prying apart Sanity Studio code and instead about building entirely new content experiences, faster than ever.
With Sanity Dashboard (the new "home screen" of the Content Operating System), the content apps you build can be deployed independently to operate across projects and datasets.
Our team has over eight years of experience building real-time content editing experiences for content creators. The App SDK is our way of distilling all we've learned into a distributed library for developers and the content creators they empower.
The primary way to work with Sanity content has always been the Sanity Studio. Being truly "headless," it is built on the same APIs developers could use to make their applications perform queries and mutations using either the Sanity Client or the HTTP API.
However, Sanity Studio has a few opinionated user experience implementation details that authors come to expect, like the way editing a published document immediately creates a new draft or how a rendered list of documents stays updated in real-time.
It could be disorienting if you needed to build your own content review or editing experience, and what you built didn't work the same way.
So while developers have always loved Sanity Studio for its configurability and customizability, building entirely new experiences has been more difficult than we were ever happy with.
That changes now.
Headless CMSes have only ever promised multi-channel to create multiple front-ends that query and display content.
Sanity App SDK fulfills the content operating system promise by allowing the creation of multiple content editing applications.
The Sanity CLI now contains a command to bootstrap a new Sanity app, which can be deployed to the Dashboard.
npx sanity@latest init --template app-sanity-ui
This template comes preconfigured with Vite, TypeScript, and Sanity UI—what you do next is up to you.
App SDK is "headless," meaning it does not come with visual components or styles. All of its functionality is centered around querying and modifying content data. It's up to you how to style the user interface.
This template includes Sanity UI—but your custom apps can be styled with Tailwind, shadcn/ui, Bootstrap, jQuery UI, or just raw CSS if you're freaky.
For consistency and familiarity we recommend using the same component library as all Sanity apps—Sanity UI—but you're welcome to ignore our recommendation.
The React package makes available a SanityApp
provider to wrap your application to perform queries and mutations. This context also contains internal logic for optimistic edits and caching so that all queries and mutations are self-updating and super fast.
Many hooks are exported by the React package, read about them all in the documentation. Highlights include:
useDocuments
returns a live list of "document handles" when given a GROQ filter. Includes affordances for optimistic UI such as pending states, a function to load more results and best of all stays updated as edits are made to documents thanks to the Live Content API.useDocument
returns the full body of a document when given a document handle, or it can be scoped to a specific "path."useEditDocument
takes a document handle and a path to edit, with optimistic, per-render local-first updates and auto-magically handling the creation of draft documents when starting with a published ID.useApplyActions
provides a function into which multiple document actions can be made. Whether they are edits, publishes, deletions or more. This makes bulk-editing of documents possible and simple.Sanity App SDK pairs nicely with Sanity TypeGen (currently in Beta) for type safety across all your schema types and query responses.
See the documentation on how to setup TypeGen with App SDK apps.
Every App built with the SDK starts with the SanityApp
wrapper, configured with settings for multiple projects.
You read that correctly, multiple projects. Unlike the Studio which could only ever target a single project and dataset. App SDK apps are designed to span across multiple configurations.
// App.tsx
import {type SanityConfig} from '@sanity/sdk-react'
import {SanityApp} from '@sanity/sdk-react'
import {ExampleComponent} from './ExampleComponent'
import './App.css'
export function App() {
// apps can access many different projects or other sources of data
const sanityConfigs: SanityConfig[] = [
{
projectId: 'project-id',
dataset: 'dataset-name',
}
]
return (
<div className="app-container">
<SanityApp config={sanityConfigs} fallback={<div>Loading...</div>}>
{/* add your own components here! */}
<ExampleComponent />
</SanityApp>
</div>
)
}
export default App
The SanityApp
context will handle deduplicating, memoizing and optimizing every fetch for content, so there's no need to do your own caching or memoization.
You may be used to querying all required content in a single, huge GROQ query passed into client.fetch().
But most SDK apps begin with a fetch for documents of a certain _type
, and resolve each matched document individually.
// DocumentsList.tsx
import {useDocuments} from '@sanity/sdk-react'
import {DocumentTitle} from './DocumentTitle'
export function DocumentsList() {
const {data} = useDocuments({filter: '_type == "event'})
return (
<ul>
{data.map((doc) => (
<li key={doc.documentId}>
<DocumentTitle {...doc} />
</li>
))}
</ul>
)
}
Rendering a live-updating list of documents can be done with the useDocuments
hook. By default the hook will only return up to 25 documents, and more can be fetched via the loadMore
function returned by the hook.
For improved performance, the data returned by this hook is deliberately kept small. An array of "document handles." Each document's _id
and _type
fields, represented as documentId
and documentType
.
// DocumentTitle.tsx
import {type DocumentHandle} from '@sanity/sdk-react'
import {useDocument} from '@sanity/sdk-react'
export function DocumentTitle(props: DocumentHandle) {
const {data} = useDocument(props)
return <h2>{data.title}</h2>
}
For high-performance, real-time updates the useDocument
hook retrieves the content of an entire document, or specific values using a path. This hook can be intensive, so it's best used lightly. Fortunately there are hooks for memory friendly data fetching and rendering.
// DocumentDetail.tsx
import {type DocumentHandle} from '@sanity/sdk-react'
import {useDocumentProjection} from '@sanity/sdk-react'
export function DocumentDetail(props: DocumentHandle) {
const {data} = useDocumentProjection({
...props,
projection: `{ title, categories[]->{title} }`
})
return (
<div>
<h2>{data.title}</h2>
<p>{data.categories.map((category) => category.title).join(', ')}</p>
</div>
)
}
This hook allows you to select individual attributes from a document—or transform them since GROQ is so flexible—as well as resolve references.
Now for some real magic.
// EditDocumentTitle.tsx
import {type DocumentHandle} from '@sanity/sdk-react'
import {
useDocument,
useEditDocument,
} from '@sanity/sdk-react'
export function EditDocumentTitle(props: DocumentHandle) {
const handleAndPath = {
...props,
path: 'title',
}
const {data: title} = useDocument(handleAndPath)
const editTitle = useEditDocument(handleAndPath)
return (
<input
value={title}
onChange={(e) => editTitle(e.target.value)}
/>
)
}
You can edit any value of a document with the useEditDocument
hook. In this example simply editing a string
type field value with an input
element.
This hook handles the logic of creating a new draft if the current document is already published. It also updates the in-memory context so your UI has local-first, optimistic updates while they are synced to the Content Lake in the background.
// PublishDocument.tsx
import {type DocumentHandle, publishDocument} from '@sanity/sdk-react'
import {useApplyDocumentActions} from '@sanity/sdk-react'
export function PublishDocument(props: DocumentHandle) {
const applyActions = useApplyDocumentActions()
const publishAction = applyActions([
publishDocument(props),
])
return (
<button onClick={() => publishAction}>Publish</button>
)
}
To take actions on a single, or multiple, documents the useApplyActions
provides a function which accepts an array of action functions. Use this to publish or discard drafts, or create granular edits across multiple documents in one transaction.
More hooks are exported by the React package, read about them all in the documentation.
The App SDK has been designed and built in close consultation with many of our largest customers who have created their own bespoke Sanity content apps or deep Sanity Studio customizations.
We can't wait to see what you'll build. If you'd love to show it off, tag us on social media or join our community on Discord and post up in the Showcase channel.