The reactive session
How a reactive session projects one workflow instance for a UI: what keeps it current, what each session state means, and when a preview becomes a commit.
Early access
Workflows is in early access, built in public. Read How early access works before you rely on it.
A reactive session is the live, UI-facing view of one workflow instance. Use it when you are building a custom Workflows surface that must stay current as the instance, referenced content, or guards change.
An evaluation is the engine’s current read-only view of stages, activities, fields, and action verdicts. The session refreshes that evaluation as observed content changes. Writes happen only when your interface calls tick, fireAction, or editField.
What a session is bound to
A session is bound to one instance ID and one store observer. The observer is what an adapter supplies, so the session itself is store-agnostic: the same evaluation and the same commands appear whether the host is Sanity Studio, an App SDK application, or a custom adapter. Swapping the instance ID resets the projection, and the previous instance's evaluation never carries over into the next one.
Instance previews
A preview is a reduced instance row for list and dashboard surfaces: current stage, list-relevant fields, open activities, and lifecycle timestamps, without the definition snapshot or the audit history. useInstancePreviews in @sanity/workflow-react holds a live set of them. It takes an engine and a store observer, so an adapter supplies the observer: @sanity/workflow-studio wraps the hook as useWorkflowInstancePreviews, and an App SDK application builds one with makeSdkObserver from @sanity/workflow-sdk/observer. See Build a workflow interface with the App SDK for a worked example.
const {previews, loading, drained, unreadable, error} =
useInstancePreviews({
engine,
observer,
filter: {includeCompleted: true},
pageSize: 500,
})previews is undefined until the first page lands, then stays newest-first. drained means every matching page has landed, so counts are totals. Before that, a count is a minimum. Render unreadable rows and error explicitly. A fetch error preserves pages that already landed.
The hook owns paging. pageSize defaults to 500, and the filter it accepts cannot set limit, before, or ids, because the hook derives them. Leave interPageDelayMs at its 0 production default; a delay only helps when watching pages arrive one at a time in a development harness. When the change feed reconnects after a failure it emits a reset, and the hook re-drains the partition so additions, edits, and deletions missed during the gap all land.
How the session stays current
The adapter observes the instance, the documents its conditions read, and its active guards. The session waits for those sources before setting ready and producing its first evaluation. Later emissions recompute that evaluation automatically.
A command still commits through the engine. After tick, fireAction, or editField settles, the session evaluates the committed result immediately instead of waiting for the observer to echo the write.
In a custom App SDK UI, the adapter observes synchronized documents from the Content Lake and feeds them into the reactive session. The editor can preview a tentative field value before the session invokes an engine commit and returns the settled evaluation.
Session states
invalidmeans an engine-owned document cannot be read safely. A document from a newer model needs a newer engine. A document with the wrong shape needs investigation. Show the reader what to do about it, instead of a spinner.errormeans an observer read failed. The state clears when the stream recovers.evaluationErrormeans the last evaluation attempt failed. Render it before treating a missingevaluationas loading. It clears after a successful attempt or when the instance ID changes.!readymeans the session is still synchronizing. A missingevaluationis a loading state only whileevaluationErroris undefined. Switching instance IDs clears the previous evaluation while the new instance synchronizes.readywith anevaluationmeans the interface can render stages, activities, fields, and action verdicts.
Preview, then commit field edits
Use previewField while a person is typing. It changes the projected field value and any advisory verdicts derived from that value, but it never changes the committed stage, activity status, or history.
Every editField call is a real engine commit with history, a guard refresh, and transition evaluation, written under a revision check. A picker such as an actor, date, or boolean fires one discrete change, so committing on change is fine there. Free text such as a string or a note body has to wait for a deliberate boundary instead: blur, Enter, or Save. A commit per keystroke writes a history entry each time and races the revision check.
Call discardFieldPreview when editing ends without a commit. The next evaluation returns to the committed value.
Action verdicts
Whether an action appears, and whether it can be fired, are properties of the evaluation rather than of the authored definition. The evaluated action model is described in Activities and actions.
- A failed
filterremoves the action from the interface. - An action with
whenis a cascade trigger, not a button. Describe what it will do fromwhenInsight. See Evaluation insights for the complete explanation contract. - For a visible manual action, use its verdict to decide whether the control is enabled and to explain why it is disabled.
Choose an integration
- For Workflows in Sanity Studio, use the Studio plugin. It provides the ready-made editor and workflow interfaces.
- Use
@sanity/workflow-studiofor a custom Studio integration such as a Document Action, dialog, tool, pane, or input. - Use
@sanity/workflow-sdkin an App SDK application. It supplies the store observer the session needs. See Build a workflow interface with the App SDK for setup and resource routing. - Use
@sanity/workflow-reactdirectly for the hooks an adapter does not re-export, such asuseInstancePreviews. Its@sanity/workflow-react/observersubpath is the adapter SPI: reach for that only when building a new store adapter. See Custom reactive adapters for the observer contract.
Reference
The hook takes engine and instanceId. Pass grantsFromPath as well when the definition's conditions use $can. It returns the current reactive state and the commands that preview or commit changes:
evaluation
WorkflowEvaluation | undefined
The latest WorkflowEvaluation that resolved successfully. It is undefined before the first success and resets when the instance ID changes. A later failed attempt leaves the last successful value in place.
ready
boolean
True after the instance, watched documents, and guards have synchronized. It is false while the session is invalid or a live read has failed. An evaluation failure does not change it.
invalid
InvalidDoc | undefined
Describes an engine-owned document that failed validation. Show remediation instead of continuing to render workflow controls.
error
unknown | undefined
The current observer failure. It clears when the live stream recovers.
evaluationError
unknown | undefined
The last evaluation failure. It clears after a successful evaluation attempt or when the instance ID changes. It does not change ready.
guards
readonly MutationGuardDoc[] | undefined
The live guard list. It is undefined before the first successful guard read or when that stream is invalid.
tick
() => Promise<OperationResult>
Re-evaluates and advances the instance through automatic actions and transitions, then commits the result.
fireAction
(args: {activity: string; action: string; params?: Record<string, unknown>}) => Promise<OperationResult>
Fires one named activity action with optional parameters and commits the result.
editField
(args: {field: EditableFieldEvaluation; mode?: EditMode; value?: unknown} | {target: EditFieldTarget; mode?: EditMode; value?: unknown}) => Promise<OperationResult>
Commits one declared editable-field operation. Call it at a deliberate editing boundary.
previewField
(args: {field: EditableFieldEvaluation; mode?: EditMode; value?: unknown} | {target: EditFieldTarget; mode?: EditMode; value?: unknown}) => void
Stages an in-memory field preview and recomputes the evaluation without writing history or persisted state.
discardFieldPreview
(fieldOrTarget?: EditableFieldEvaluation | EditFieldTarget) => void
Removes the preview for one field target, or every staged preview when no target is supplied.
Next steps
- Build a workflow interface with the App SDK: build a custom workflow interface on the App SDK adapter.
- Custom reactive adapters: implement the observer contract for another store.
- API reference: inspect the complete session and adapter APIs.
Visiting agent?
Workflows includes an MCP server for inspecting, operating, authoring, validating, and deploying workflows. Ask your human to set up the MCP server.

