Editorial Workflows

Cookbook

Worked, runnable workflow examples for Sanity: editorial review, AI content pipelines, coordinated releases, and more, each a complete definition.

Prerelease

The examples run from the smallest useful review to a coordinated multi-document release. Between them they cover the constructs you reach for most often:

  • Every example is built from stages, transitions, and the actions that do the work.
  • Review loops send rejected work back to drafting, and claims let a writer take the open commission.
  • Triggers fire the effects that generate drafts and run the checks.
  • Conditions route work to a human only when a check fails.
  • Subworkflows give each document in a release its own review.
  • Guards coordinate a document lock across engine-mediated workflow surfaces until its review approves; Content Lake enforcement for deployed guards has not shipped.

Editorial review

One article from assignment to publish, gated by a single editor.

  • Assignment: a writer takes the open commission.
  • Drafting: the assigned writer writes, then submits for review.
  • Review: a desk editor approves it or sends it back with a reason; rejection loops to drafting.
  • Published: terminal.

This is the loop the other examples build on, so read it first.

Cookbook: Editorial review

AI content pipeline

An agent drafts, automated checks run, and a person steps in only when a check flags the piece.

  • Drafting: an AI effect generates the draft.
  • Checks: automated effects score it (brand voice, SEO, links, facts) and flag what fails.
  • Verification: opens only for flagged pieces; clean ones skip straight to publish.
  • Published: terminal.

Cookbook: AI content pipeline

Coordinated release

Many documents reviewed and published together as one Content Release.

  • Assembly: documents are grouped into a named release; each gets its own review subworkflow, and a guard coordinates the publish hold across engine-mediated workflow surfaces until that review approves.
  • Scheduled: once every document is approved, the release is scheduled for an embargoed go-live (or published on demand).
  • Published: the release publishes as one unit; effects fire the build and notifications.
  • Archived: terminal, with revert as the rollback.

Cookbook: Coordinated release

Handle a deleted subject document

Choose what happens to in-flight workflow instances when referenced content is deleted. A document-delete Function finds affected runs with instancesForDocument and can retain them or abort them through the engine without deleting their audit records.

Cookbook: Handle a deleted subject document

Running a workflow

A definition is inert. To run it you connect a runtime: the things that call the engine when a person acts or content changes. Most deployments use two surfaces and add more as needed.

Use the Studio plugin for the ready-made human interface, or the Studio/App SDK adapters when building a custom workflow UI.

Sanity Functions are the machine surface, usually doing three jobs:

  • A document-change Function calls tick on the affected instance when its subject or a watched document changes, so triggers and transitions re-evaluate against the new content.
  • A scheduled Function calls tick on a timer to fire deadline triggers and transitions (conditions over $now), since the engine keeps no clock of its own; nothing time-based happens unless something ticks it. The same heartbeat fires any trigger left armed because an earlier caller’s token could not fulfill its roles.
  • An effect drainer calls drainEffects to run queued effects: the AI calls, notifications, and build hooks a workflow emits. Delivery is at-least-once (a handler may run twice around a crash or an expired claim lease), so write handlers idempotently: check the run’s effectKey against effectHistory before irreversible work, and derive external-system ids from it.

Any service that can call the engine may drive a workflow. Choose one owner for each trigger source and make repeated delivery safe.

How much to enforce

Choose the mechanism that matches the guarantee you need. Engine verdicts and guards improve workflow coordination but remain advisory today; dataset access control is the current hard boundary. These mechanisms can be combined, but they are not three equivalent levels of enforcement.

Engine checks are advisory. Action filters, roles, and requirements decide what appears in workflow surfaces and fail fast before a doomed engine commit. They are not a security boundary: anyone whose token permits a raw Content Lake mutation can bypass the engine. For cooperative teams and internal tools, this is useful guidance with honest limits.

Add guards when workflow-aware surfaces should preview and explain a publishing hold. See Guards and enforcement for the current contract.

When a rule must hold regardless of which client writes, enforce it in the Content Lake. Dataset access control and custom roles determine server-side whether a token may publish or write in a dataset, independently of the engine. Today this is the hard boundary; a deployed guard becomes one only when Content Lake guard enforcement ships.

The editorial example relies on advisory workflow guidance. The coordinated release combines role-based access, guards that coordinate its engine-mediated document paths, and Content Lake access control on the release.

Tags and client factories

Two pieces of plumbing show up in every real deployment.

Use a distinct tag for each environment so definitions and instances from production, staging, and local development remain separate.

Use resourceClients when workflow data and governed content need different clients, or when runtime-supplied references may target another resource. See Global document references for routing and admission rules.

Resource aliases keep definitions portable: reference a named resource in the definition, then bind that alias to the appropriate project and dataset in each deployment.

Every @content: reference expands to a physical global document reference (GDR) at deploy, so moving between environments means deploying the same source with a different map; bare ids and physical GDRs are unchanged.

This is the deploy-time sibling of resourceClients, which routes reads at runtime.

The same definitions deploy under each tag, with the alias bound to that environment’s dataset:

Before setting expectedMinReaderModel, review Reader-model acknowledgement.

// sanity.workflow.ts
import {defineWorkflowConfig} from '@sanity/workflow-engine/define'

import {articleReview} from './article-review'

export default defineWorkflowConfig({
  deployments: [
    {
      name: 'production',
      tag: 'prod',
      expectedMinReaderModel: 4,
      workflowResource: {type: 'dataset', id: 'yourprojectid.workflows'},
      resourceAliases: [{name: 'content', resource: {type: 'dataset', id: 'yourprojectid.production'}}],
      definitions: [articleReview],
    },
    {
      name: 'staging',
      tag: 'staging',
      expectedMinReaderModel: 4,
      workflowResource: {type: 'dataset', id: 'yourprojectid.workflows-staging'},
      resourceAliases: [{name: 'content', resource: {type: 'dataset', id: 'yourprojectid.staging'}}],
      definitions: [articleReview],
    },
  ],
})

Next steps

  • Getting started walks the shortest path from nothing to a running workflow.
  • At a glance explains the model behind the DSL.
  • Reference has every construct and verb, with exact shapes.
  • Test your workflows shows how to cover a recipe’s paths with the in-memory bench before deploying it.

Visiting agent?

Was this page helpful?