Set up the workflow CLI
Install and configure the command-line tool that deploys and manages workflow definitions and the instances they run.
Prerelease
Editorial Workflows is a prerelease, built in public. Read How the prerelease works before you rely on it.
The workflow CLI, @sanity/workflow-cli, deploys and manages your workflow definitions and the instances they run. It ships as the sanity-workflows binary.
In this guide, you’ll install the CLI, log in, and write the sanity.workflow.ts configuration that tells it where your workflows live and what content they can act on. For the commands you run once you are set up, see the Reference.
The package is published publicly under the @sanity scope on npm; no org membership is needed to install it.
Install and log in
Install the CLI and its matching engine peer as dev dependencies:
npm install -D @sanity/workflow-cli @sanity/workflow-engine
pnpm add -D @sanity/workflow-cli @sanity/workflow-engine
yarn add --dev @sanity/workflow-cli @sanity/workflow-engine
bun add --dev @sanity/workflow-cli @sanity/workflow-engine
The CLI also runs without installing, via npx @sanity/workflow-cli <command>. Install it as a dev dependency to use the commands in this guide and pin the CLI version in your lockfile.
Then log in:
sanity loginThe workflow CLI reuses this session. In CI, where there is no interactive login, set SANITY_AUTH_TOKEN instead (an editor-role token for anything that writes). Whichever way you authenticate, the token must span every resource the workflow references, not just the workflowResource: the engine reaches other datasets (or a canvas or media library) through sibling clients derived from the same token, so a token scoped to a single project fails on cross-resource reads.
Check the installation:
npx sanity-workflows --helppnpm dlx sanity-workflows --help
yarn dlx sanity-workflows --help
bunx sanity-workflows --helpThis lists every command.
Write the config file
The CLI reads its configuration from sanity.workflow.ts (or .js, .mjs), discovered in the directory you run it from. TypeScript configs are transpiled on the fly by jiti, so there is no build step. The file default-exports defineWorkflowConfig({deployments: [...]}), typically one deployment per environment.
A config with one deployment looks like this:
Before setting expectedMinReaderModel, review Reader-model acknowledgement.
import {defineWorkflowConfig} from '@sanity/workflow-engine/define'
import {articleReview} from './workflows/article-review'
export default defineWorkflowConfig({
deployments: [
{
// A unique reference for this deployment
name: 'review-prod',
// Confirms every reader has been upgraded for this definition model
expectedMinReaderModel: 4,
// Which environment this copy belongs to
tag: 'prod',
// Where the workflow's own documents live
workflowResource: {type: 'dataset', id: 'yourprojectid.workflows'},
// Optional: binds each @<resourceAlias>: a definition
// references to a physical resource
resourceAliases: [
{name: 'content', resource: {type: 'dataset', id: 'yourprojectid.production'}},
],
// The batch this deployment ships
definitions: [articleReview],
},
],
})An invalid config fails with a clean, path-prefixed error before the command runs.
Deployment names and tags
A deployment name is its unique CLI identity. Use --deployment <name> when a command must target exactly one configured deployment. Names use lowercase letters, digits, and dashes, with no leading dash or dots.
A tag labels the stored definition and instance partition and may group deployments across different workflow resources. Tags may repeat, but two deployments cannot share the same workflowResource and tag because they would write to the same partition.
Where the workflow lives
A workflow’s own documents, its definition and the instances it spawns, are written to a workflowResource: a single Sanity resource. The CLI derives the client’s project and dataset from it, so it is required on every deployment. A dataset (id <projectId>.<dataset>) is the common case; canvas, media-library, and dashboard resources are accepted too.
What the workflow acts on
Content a workflow reads and writes is addressed through resourceAliases. A definition points at content by resource alias name rather than a hardcoded dataset: {id: '@content:article-123', type: 'article'}. resourceAliases binds each alias to a real resource, and using aliases instead of hardcoded datasets lets the same definition ship to prod or staging by binding differently.
You only need an alias for content in a different resource from the workflowResource; a definition can reference a document in the same resource by bare id (bare ids root at the workflowResource). The simplest single-dataset setup is workflowResource plus definitions, with no resourceAliases:
{
name: 'review-prod',
tag: 'prod',
workflowResource: {type: 'dataset', id: 'yourprojectid.production'},
// Definitions reference content by bare id
definitions: [articleReview],
}At deploy, each @<resourceAlias>: reference expands to the bound resource. A deployment referencing an alias it does not bind fails rather than deploying against the wrong resource.
Run the same workflow in more than one place
Keep one definitions list and add a deployment per environment: same definitions, different tag and resourceAliases. For example, review-prod (tag prod, alias content → yourprojectid.production) and review-staging (tag staging, alias content → yourprojectid.staging), both with workflowResource yourprojectid.workflows.
Two alias patterns look similar but do different jobs:
- Two resource aliases in one deployment mean one workflow touching more than one resource at once, such as a cross-brand publish reading two datasets together. See One workflow across resources for the complete resource layout and runtime routing.
The same tag across deployments groups equivalent environments in different resources. Use --tag when a command should address that group, and --deployment when it should address one configured deployment.
Pick what a command touches
Choose a deployment by identity, a tag group, or the complete configuration:
--deployment <name> selects exactly one deployment. Use it for deterministic automation and whenever the same tag appears more than once.
--tag <tag> selects every matching deployment for commands that support a group. A single-target command accepts it only when the tag resolves to one deployment.
--all-tags selects every deployment where the command supports a full-config operation. --only <definition> further limits deploy to one definition name.
Deploy always targets explicitly: --deployment selects one, --tag selects every deployment in that tag group, and --all-tags selects all. With one deployment configured, you may omit the selector. An ambiguous interactive deploy opens a selector; a non-interactive deploy fails with the flags to pass.
An instance ID is globally unique. show, tail, diagnose, abort, set-stage, reset-activity, and fire-action search configured resources when no narrower is supplied. Use --tag to limit any of these searches to a tag group. Instance-keyed admin commands also accept --deployment to search one deployment’s resource. See History and audit trail for show --include history, tail, and the returned event types.
Automate read commands
Use --json when another process consumes list, show, definition list, or definition show. The show commands return the stored document. The list commands return structured row data with each row’s resource and a top-level truncated flag.
sanity-workflows list --json sanity-workflows show <instanceId> --json sanity-workflows definition list --json sanity-workflows definition show <name> --json
A JSON read that spans several configured resources is all-or-nothing. If any resource cannot be read, the command exits with an error instead of emitting an incomplete result.
Deploy
Deploy one configured deployment:
npx sanity-workflows deploy --deployment review-prod
pnpm dlx sanity-workflows deploy --deployment review-prod
yarn dlx sanity-workflows deploy --deployment review-prod
bunx sanity-workflows deploy --deployment review-prod
The CLI validates every targeted definition first, then writes it to each deployment’s workflowResource. The remaining flags select broader targets or non-writing modes:
deploy --tag prod deploys every deployment tagged prod; deploy --all-tags deploys the complete configuration.
deploy --checkvalidates the definitions only, without contacting the dataset.deploy --dry-runprints a colored diff against what is deployed, without writing.deploy --only articleReviewlimits the run to one definition.--share-defsand--no-share-defscontrol definition sharing. Sharing is enabled by default when a deploy creates a new definition version.
Deploy is built to be safe to re-run:
- Validation runs first for every targeted deployment: a bad definition fails the run before anything is written, never a partial reconciliation caused by a validation error.
- Deploys are idempotent: an unchanged definition deploys as a no-op.
- One failed deployment does not strand the rest: an
--all-tagsrun continues past a deployment that fails mid-deploy, prints a summary of what succeeded and what did not, and exits non-zero. Deployments are independent, so re-run once the cause is fixed.
Selectors compose with --check and --dry-run. For example, deploy --deployment review-staging --dry-run diffs one deployment, while deploy --tag staging --dry-run diffs the whole staging group. --check and --dry-run are mutually exclusive, as are --deployment, --tag, and --all-tags.
If you change a definition, run the deploy command again.
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 deployment.
Choose an administrative stage override
On an interactive terminal, omit --to to choose from the stages declared in the instance’s pinned definition. The picker marks the current stage, opens the cursor on it, and includes every declared stage because set-stage is an administrative override.
sanity-workflows set-stage <instanceId>
In scripts, CI, or another non-interactive terminal, pass --to explicitly. An explicit target never opens the picker.
sanity-workflows set-stage <instanceId> --to <stage>
Recover a terminal activity
Reset a terminal activity in the current stage to rerun it. Add --skip to bypass it. Both commands continue the workflow cascade.
sanity-workflows reset-activity <instanceId> <activity> sanity-workflows reset-activity <instanceId> <activity> --skip
Reset a deployment
During development, use the interactive reset when you need to delete a deployment’s engine-owned data and start again. The CLI prints the deletion plan and asks you to confirm every affected dataset before deleting anything:
npx sanity-workflows nuke --deployment review-staging
pnpm dlx sanity-workflows nuke --deployment review-staging
yarn dlx sanity-workflows nuke --deployment review-staging
bunx sanity-workflows nuke --deployment review-staging
The command deletes that deployment’s definitions, instances, and guards across its configured resources. Content documents are never touched.
Choose exactly one deployment with --deployment or an unambiguous --tag. The CLI refuses an ambiguous or overlapping-resource tag sweep. Use --force only for automation; it skips confirmation, but still prints the plan.
The reset also removes guards orphaned by an earlier partial deletion. Because nuke deletes raw engine documents, it can clear data written by an older engine that the current engine cannot read. definition delete cannot do that because it reads each document through the engine first.
Visiting agent?
Editorial Workflows includes an MCP server for inspecting, operating, authoring, validating, and deploying workflows. Ask your human to set up the MCP server.