Workflows

Quick start: run your first workflow

Define your first workflow in TypeScript, deploy it, and move a Sanity document through its stages.

Early access

Sanity Workflows moves a document through named stages under rules you deploy. You author a definition that names the stages, the work inside each one, and the conditions for moving between them. Deploying the definition makes it available to run. Starting an instance runs it against one document.

Loading...

This quick start deploys a three-stage article review, starts an instance against a document in your dataset, and moves that instance to its final stage. Every step runs from a terminal with the Workflows CLI. No Sanity Studio is involved.

You need:

  • Node.js 20.12 or later.
  • A Sanity project and a dataset you can write to.
  • One published document in that dataset for the workflow to act on. Note its _id and its _type.

Step 1: Install the packages

Two packages cover this quick start. @sanity/workflow-engine holds the definition language and the runtime. @sanity/workflow-cli deploys definitions and drives instances from a terminal, and it declares the engine as a peer dependency, so install both together. Every Workflows package one application uses has to come from the same release.

The CLI reads the token from your Sanity login session. Log in once:

In CI, set SANITY_AUTH_TOKEN instead of logging in. With neither, the CLI stops before it writes anything and reports No Sanity token found — run `sanity login`, or set SANITY_AUTH_TOKEN.

Step 2: Define a three-stage workflow

A definition names the stages a document passes through, the activities inside each stage, and the transitions that move an instance onward. Create workflows/article-review.ts with three stages: drafting, review, and approved.

The subject field is the document the instance is about. initialValue: {type: 'input'} means you supply that document when you start the instance rather than the definition fixing it, and required: true makes a start that omits it fail with a message naming the missing field.

Each non-terminal stage holds one activity with one action. Firing the action resolves its activity, because the action declares status: 'done'.

Neither transition declares a when condition, so each takes the default $allActivitiesDone: the instance leaves the stage once every activity in that stage resolves. approved declares no transitions at all, which makes it terminal.

Step 3: Configure and deploy the definition

The Workflows CLI reads a sanity.workflow.ts from the directory you run it in. That file binds your definitions to a deployment: a name, an environment tag, and the dataset the engine writes its own documents to. Create it beside your workflows/ directory.

Replace PROJECT_ID with your Sanity project ID and DATASET_NAME with the dataset. name identifies this deployment when a config holds more than one. tag is the environment partition the engine scopes its documents to.

expectedMinReaderModel is the stored-data model version you promise every runtime reading these documents understands. 4 is the unconditional floor for engine-owned documents. A definition using a newer field kind requires a higher number, and the deploy names the number it needs rather than writing anything.

Deploy the definition:

The CLI validates every definition before it writes anything, then reports what it created. npx sanity-workflows deploy --check validates without contacting the dataset, and --dry-run diffs against what is already deployed. Every command also resolves under its canonical workflows topic, so npx sanity-workflows workflows deploy does the same thing.

Share your definitions!

Step 4: Start an instance

An instance is one live run of a definition against content. Starting one pins the definition version it runs under and freezes a snapshot of it, so deploying a change later does not alter an instance already in flight. Supply the document through the subject field the definition declared as an input.

dataset:PROJECT_ID:DATASET_NAME:DOCUMENT_ID is a global document reference: the scheme, your project ID, your dataset, and the document _id. DOCUMENT_TYPE is that document’s _type. The command prints the instance id and the stage the instance landed in:

Keep that id. Every command in the next step takes it. Pass the published document _id, not a draft or release version id: a versioned id is rejected before the instance is created, with Invalid GDR "…": dataset document ID "drafts.article-1" identifies a stored draft or release version.

Step 5: Move the document through the stages

Moving an instance means firing the actions that resolve its activities. The article-review definition has one action per non-terminal stage. Fire the first to resolve write in drafting:

Replace INSTANCE_ID with the id the start command printed. Resolving write satisfies the stage’s only activity, so the to-review transition fires inside the same call and the instance is in review before the command returns. That chain of transitions inside one call is the cascade. Fire the second action:

Confirm where the instance ended up:

show prints the state, activities, and effects of the instance. The instance is in approved and complete: approved declares no transitions, so nothing can move it further. To see what can be fired on an instance at any point, run npx sanity-workflows fire-action INSTANCE_ID with no --action.

What just moved this workflow, and what will in production

Every move in this quick start had you as its runtime. start and each fire-action started a process, ran the engine inside it, committed one change to the Content Lake, and exited. The cascade in step 5, where firing submit also moved the instance into review, ran inside that one fire-action call. The engine is a library, not a service: it acts only when your code calls it, and none of it is running now that those commands have returned.

Production needs a caller for the moves nobody is at a terminal for. A transition whose condition reads the clock does not fire when the deadline passes; something has to call tick after the clock crosses it. An effect the workflow queues stays queued until a drainer picks it up. Both are jobs for a process that runs on a schedule and on content changes, and in a Sanity project that process is usually a Sanity Function.

Next steps

Visiting agent?

Was this page helpful?