Workflows

Configure and deploy workflow definitions

Install and authenticate the workflow CLI, write the sanity.workflow.ts config that binds your definitions to a Sanity resource, and deploy them to one environment or several.

Early access

Use @sanity/workflow-cli to store workflow definitions in the Content Lake. It reads sanity.workflow.ts from the current directory, validates the definitions, checks referenced project roles during deployment, and writes valid definitions to the configured workflow resource. It ships as the sanity-workflows binary. A workflow engine runs the deployed definitions.

This guide installs and authenticates the CLI, writes the config that binds your definitions to a resource, and deploys them to one environment and then to several. For what each command and flag does once you are set up, see the Workflow CLI command reference.

Before you start, you need:

  • Node.js 20.12 or later.
  • @sanity/workflow-cli 0.32.0, and the matching @sanity/workflow-engine.
  • At least one workflow definition authored with defineWorkflow. See Definitions, instances, and stages, or work through the quick start first.
  • A Sanity project, and a dataset for the engine to keep its own documents in. That can be the dataset your content lives in or a separate one.
  • A Sanity login session, or a token with the editor role. The next section covers both.

If your definitions reference project roles, the deployment identity must also be able to read that project's role catalog and member directory. Create the referenced roles in the project before deployment.

Install and authenticate the CLI

The CLI ships as @sanity/workflow-cli with a single binary named sanity-workflows. Run it with npx rather than installing it globally. npx uses the copy in your project when there is one, so adding it as a development dependency is how you pin the version a team and a CI job share.

Install @sanity/workflow-engine alongside it. The CLI declares the engine as an exact peer dependency and does not bundle it, because your config imports defineWorkflowConfig from it, and the two packages release in lockstep at identical version numbers. Without the engine, every command stops at Cannot find module '@sanity/workflow-engine/define'.

Authenticate once with the Sanity CLI. Every workflow command reuses that session.

In CI there is no session to read, so set SANITY_AUTH_TOKEN instead. An explicit SANITY_AUTH_TOKEN always wins over a login session, which also makes it the way to run a one-off command as a different identity. Anything that writes needs an editor-role token. Never commit it: read it from the secret store of your CI provider and pass it through the environment.

Scope the token to every resource, not one

One more environment variable exists: SANITY_API_HOST overrides the API host the CLI talks to. Leave it unset unless you have been told to point at a non-production API.

Write the workflow config

Every workflow command reads a sanity.workflow.ts from the directory you run it from. The file default-exports a config built with defineWorkflowConfig, declaring one deployment per environment you ship to. TypeScript configs are transpiled on the fly, so there is no build step, and a sanity.workflow.js or sanity.workflow.mjs works the same way.

The smallest config that deploys is one deployment, one resource, and one definition:

Replace <your-project-id> with your project ID. A dataset resource ID is always <projectId>.<dataset>. Anything else fails at load with invalid dataset resource id — expected "<projectId>.<dataset>".

Four keys carry the setup. name is the CLI identity of the deployment, the one --deployment matches. tag is the environment partition its stored definitions and instances are scoped to. workflowResource is the Sanity resource that holds those documents. resourceAliases binds the content a definition acts on, and you can leave it out when that content already lives in the workflowResource. Deployments, tags, and resources has the full field reference and the model behind it.

An invalid config fails with a clean, path-prefixed error before the command runs. Getting expectedMinReaderModel wrong is the one failure worth recognizing on sight: omit it and deploy stops with Reader-floor acknowledgement: and the model your definitions actually need. Keep it a reviewed literal, and read Upgrade Workflows packages before changing it.

Run the same workflow in more than one place

Running the same definitions in staging and production is a second entry in deployments: one definitions list, a different name, tag, and alias binding per environment. Make sure the workflowResource and tag pair differs between them, either by pointing them at different datasets or by keeping one dataset and giving them different tags.

This config keeps engine documents out of the content datasets, which is why each deployment binds a content alias to the dataset its articles actually live in. The two environments stay independent because their workflowResource and tag pairs differ, which is explained in Deployments, tags, and resources.

Once the config declares more than one deployment, commands stop guessing which one you mean. An interactive terminal prompts you to choose, and a non-interactive shell fails with Multiple deployments configured — pass --deployment or --tag. Name the target explicitly in CI. See Select a deployment, a tag, or an instance for what each selector means on each command.

Deploy

Deploying has three gears, and running them in order turns most deploy failures into local ones. --check validates your definitions and stops: it never contacts the dataset and never resolves a token, so it runs offline and belongs in a pre-commit hook or a pull request check. --dry-run adds a colored diff against what is already deployed, and still writes nothing. Not both at once: passing both fails with Pass either --check or --dry-run, not both.

Deployment checks referenced role names against the project’s role catalog. An unknown role stops the deployment with the definition name and field path. A known role with no current human member produces a warning. Neither --check nor --dry-run performs this directory check.

To narrow a run to one definition, add --only <name>. If you change a definition, run the deploy command again. Re-running is safe: a version comes from the content of the definition, so a deploy that changes nothing writes nothing.

Before adopting singular assignment lists, follow Upgrade Workflows packages. An unchanged deployment does not update the stored definition’s modelVersion. After deploying changed definition content, inspect the latest stored version:

Replace article-review and prod with your definition name and tag. Check version, modelVersion, and minReaderModel in the returned JSON. A new definition version that uses singular assignments has modelVersion: 9 and minReaderModel: 9. Existing instances keep the definition version they started with.

--all-tags is deliberately explicit: deploying everything is never the default. A failing deployment does not stop the others. Fix the cause and re-run; see Exit codes for how a multi-target run reports and exits.

Deploying puts the definition in the Content Lake. It does not make anything run. Nothing advances a workflow until your code calls the engine, so decide what your runtime is before you rely on deadlines or queued effects firing. See Run Workflows with Sanity Functions.

Definition sharing

Definitions are shared with Sanity by default when a deploy creates a new version. Pass --no-share-defs to opt out for one invocation. A failed share warns but does not fail the deploy.

Next steps

Visiting agent?

Was this page helpful?