Workflows

Build a workflow interface with the App SDK

Render live workflow state and commit actions from your own App SDK application: mount a session, handle its states, render activities and fields from the evaluation, and list instances.

Early access

This page is for applications outside Studio. To build a Document Action, tool, pane, input, or dialog inside Studio, see Create a workflow-powered Document Action. Choose an integration compares the packages.

When the Studio plugin does not fit the interface you need, use @sanity/workflow-sdk to build the workflow experience in an App SDK application. It supplies live workflow state and the engine operations that change it.

This guide follows one application path: connect an instance, render its current work, commit user actions and field edits, then add instance lists. Use Reusable UI components for assignment and date controls. For the underlying live-data model, see The reactive session.

Install the runtime

Use @sanity/sdk and @sanity/sdk-react 3.1 or later in the 3.x line. Install matching SDK versions and one matching version for every @sanity/workflow-* package.

SDK 3.1.0 can resolve @sanity/mutate 0.18.1, which can leave document reads pending with Sanity client 8. Before installing, merge this override into your application’s root package-manager configuration:

Install the App SDK adapter, shared controls, and their host dependencies:

@sanity/workflow-react supplies the shared reactive hooks used in this guide. The UI examples use Sanity UI 4 and must render inside its theme provider.

Run npm ls @sanity/sdk @sanity/mutate or pnpm why @sanity/mutate. Confirm that SDK 3 resolves Mutate 0.18.2; other dependency branches may use different versions. Commit the configuration and updated lockfile.

Keep the override until your SDK release requires Mutate 0.18.2 or later and excludes 0.18.1. After removing it, reinstall and verify the dependency tree again.

How workflow data reaches your UI

Reading a workflow instance document is not enough to render its UI. What the current user can see and do also depends on referenced content, guards, and field previews, and those inputs can change while the screen is open.

  • The engine loads the instance and evaluates its definition against the content it references.
  • The App SDK adapter watches those documents and feeds changes back into the engine.
  • The session exposes the latest evaluation: stages, activities, fields, actions, and the reasons they are or are not available.
  • Your controls call the session’s commit verbs. Successful commits update the instance and produce a new evaluation.

Create a live session

Create one workflow session for the instance currently open in a detail view. The session keeps that instance's evaluation current and provides the operations that change it. Pass the engine and instance ID to useWorkflowSession. Use the hooks in List workflow instances to discover instances without creating a session for every row.

Do not edit workflow instances as content

Mount it inside SanityApp, configured for the dataset your content lives in:

The App SDK client token identifies the actor and authorizes the session’s commits.

Render one workflow

A workflow screen must distinguish “still loading” from “this instance cannot be read.” The session reports that state and exposes the evaluation, a read-only projection of the workflow for the current actor, plus the operations that commit changes.

Handle invalid, error, and evaluationError before loading. Render the workflow only when ready is true and an evaluation is available. Session states explains what each one means:

A failed evaluation retains the last successful evaluation. Check evaluationError before displaying that value as current. Loading content is unresolved; it is not evidence that a referenced document was deleted.

Render activities and actions

The authored definition says which actions could exist. It does not tell your UI what is available right now. Map evaluation.currentStage.activities, then pass each entry from activity.actions to the action control. The evaluation classifies each action as filtered out, automated, allowed, or blocked:

Render editable fields

Editing every keystroke as a workflow commit would create a separate history entry for every change. The evaluation’s editableFields identify what the current actor may edit. Use previewField for immediate feedback, then editField when the user deliberately commits:

Resolve workflow actors

Workflow data stores stable actor IDs, but an interface needs names and profile details. Use sdkProjectUserDirectory to resolve an ID already present in workflow history or assignments:

An inaccessible lookup can mean the directory request failed or returned malformed data. Keep that outcome distinct from a missing user and let a later lookup retry.

Edit project-member assignments

Load people and project roles with useProjectMembers(projectId). It returns the ProjectMembersState that the picker accepts. If the role catalog cannot be read, the picker uses roles held by the loaded people. Assigning a person removes the task from other role members’ task lists, even if roles remain selected. Keep account-global user IDs and project role names as the stored values.

Headless integrations can import loadProjectRolesOrNone and ProjectRole from @sanity/workflow-sdk/project-users. The read needs sanity-project-roles: read and is cached per client and project. An unavailable catalog returns [] until the page reloads.

Render the project member directory with the controlled picker:

See Reusable UI components for the component contracts and other controls.

Explain workflow decisions

A blocked action or waiting transition needs an explanation, not only a boolean. The evaluation pairs each decision with an insight that describes the condition and its unmet parts.

Use insights to explain action and activity availability, automation, requirements, transitions, and field permissions. See Evaluation insights for every insight location and the field-centric proposal model.

For a blocked condition, render blockedBy. It lists the unmet parts of the condition in source order:

For a publish transition gated on $fields.legalApproved == true && $fields.score > 5 with both atoms unmet, this returns both. Once score passes 5, it returns ['$fields.legalApproved == true (this alone unblocks it)'].

List workflow instances

A session covers one instance. An inbox, board, or document banner has to discover which instances to show first. Use useWorkflowInstances for collections and useDocumentWorkflows for the workflows involving one document. Address that document with a global document reference:

Three hooks cover different list shapes:

  • useWorkflowInstances: a live, filterable list of full instances. Use it when the surface needs instance detail for a bounded set.
  • useDocumentWorkflows: the in-flight instances that reference one document. Use it for a document banner.
  • useInstancePreviews: a progressively hydrated list of reduced rows. Use it for an inbox, board, or dashboard that should not load every full instance.

The first two supply the store observer for you. useInstancePreviews comes from @sanity/workflow-react and takes one as an argument, so build it with makeSdkObserver:

previews is undefined until the first page lands, and drained tells you whether a count over the list is a total or a minimum. A row carries no definition snapshot, so title what you render from the deployed catalog and fall back to raw names. See Instance previews for the paging and reconnect semantics.

Where each component input comes from

The examples above pass workflow data from one session into presentation components. This map shows where each prop originates. See Reusable UI components for the component contracts themselves.

  • engine

    Engine

    Created with createEngine in WorkflowPanel.

  • Selected from a row returned by the instance-list hooks, or supplied by application routing.

  • session

    WorkflowSession

    Returned by useWorkflowSession for the selected instance.

  • evaluation

    WorkflowEvaluation

    Read from session.evaluation after the session is ready.

  • activity

    ActivityEvaluation

    One entry from evaluation.currentStage.activities.

  • action

    ActionEvaluation

    One entry from activity.actions.

  • field

    EditableFieldEvaluation

    One entry from evaluation.editableFields.

  • memberState

    ProjectMembersState

    Members, role catalog, and loading state returned by useProjectMembers.

  • fire

    function

    An application callback that awaits session.fireAction and surfaces a rejected commit.

  • projectId

    string

    The project ID from the application's SanityApp configuration.

  • Application-owned navigation callbacks, shown in the list-to-task example.

Build a list-to-task application

A usable workflow application needs both discovery and detail: list readable instances, let the user choose one, then mount a session for that instance.

Branch on loading, read errors, unreadable workflow documents, an empty result, and readable instances before navigating to a detail session:

Understand live updates

The session recomputes as watched content, committed workflow state, and field previews change. The reactive session explains watch sets, update timing, and preview behavior.

Integrating another host or data layer? Build a custom reactive adapter.

Next steps

Visiting agent?

Was this page helpful?