Custom reactive adapters
Connect Workflows to an unsupported host or data layer by implementing the store-agnostic reactive observer contract.
Prerelease
Workflows is a prerelease, built in public. Read How the prerelease works before you rely on it.
Use the Studio plugin inside Sanity Studio and the App SDK adapter in a custom application. Build another adapter only when a different host or data layer must drive the reactive session.
Build your own adapter
@sanity/workflow-react is store-agnostic. An adapter connects its hook core to a reactive document store by implementing WorkflowObserver: four useSyncExternalStore-shaped reads, two instance-preview methods, and one write:
type ObserverState<T> =
| {status: 'loading'}
| {status: 'ready'; value: T}
| {status: 'invalid'; invalid: InvalidDoc}
| {status: 'error'; error: unknown}
interface DocStore<T> {
subscribe: (onChange: () => void) => () => void
getSnapshot: () => ObserverState<T>
}
interface ObservedInstances {
instances: readonly WorkflowInstance[]
unreadable: readonly InvalidDoc[]
}
interface WorkflowObserver {
observeInstance: (instanceId: string) => DocStore<WorkflowInstance | null>
observeDocs: (
documents: SubscriptionDocument[],
perspective: WorkflowPerspective | undefined,
) => DocStore<ReadonlyMap<string, SanityDocument | null>>
observeGuards: (args: {
instanceId: string
resources: readonly DatasetResourceId[]
sharedIds?: readonly string[]
}) => DocStore<readonly MutationGuardDoc[]>
observeInstances: (query: CompiledQuery) => DocStore<ObservedInstances>
fetchInstancePreviews: (query: CompiledQuery) => Promise<readonly unknown[]>
listenInstanceChanges: (args: {
query: CompiledQuery
onEvent: (event: InstanceChangeEvent) => void
}) => () => void
ensureDocumentExists: (args: EnsureDocumentExistsArgs) => Promise<void>
}On a preview-listener reconnect, emit a reset event after the connection is restored so consumers refetch pages that may have changed during the gap. Existing custom adapters must implement both preview methods before upgrading; TypeScript reports missing methods at compile time, and incomplete JavaScript adapters fail when a preview list is used.
Every store starts in loading and resolves to ready, invalid, or error; a later successful emission clears either failure. Content documents pass through unvalidated. The instance and guard wrappers surface validation failure through the store’s wholesale invalid state. validatedInstancesStore instead validates each list row and returns {instances, unreadable} in a ready snapshot. Keep resolved snapshots referentially stable, including the shared NO_GUARDS identity for an empty guard list. The builder kit lives at @sanity/workflow-react/observer: validatedInstanceStore, validatedGuardsStore, validatedInstancesStore, combineDocStores, combineGuardStores, ensureDocumentExists, and useKeyed. Pass the completed observer to the hooks from @sanity/workflow-react.
Next steps
- App SDK: build an application with the shipped adapter.
- The reactive session: understand the live projection an adapter supplies.
- API reference: find the complete observer and hook contracts.
Visiting agent?
Workflows includes an MCP server for inspecting, operating, authoring, validating, and deploying workflows. Ask your human to set up the MCP server.