Editorial Workflows

Conditions

How workflow rules decide: GROQ conditions evaluated against the instance’s rendered scope, and named conditions that keep repeated rules in sync.

Conditions are GROQ expressions that answer a yes-or-no question about workflow state. The condition site determines what data the expression can read and what its result controls.

const articleReview = defineWorkflow({
  name: 'article-review',
  title: 'Article review',
  initialStage: 'review',
  fields: [
    defineField({type: 'subject', name: 'subject', initialValue: {type: 'input'}, required: true}),
    defineField({type: 'boolean', name: 'approved'}),
  ],
  stages: [
    defineStage({
      name: 'review',
      transitions: [
        defineTransition({name: 'publish', to: 'published', when: '$fields.approved == true'}),
      ],
    }),
    defineStage({name: 'published'}),
  ],
})

What conditions read

The engine evaluates each condition as GROQ against a context built for that decision. This is not an unrestricted Content Lake query. The condition variable reference lists the GROQ variables available at each condition site.

For a running instance, the engine builds a bounded in-memory snapshot containing:

  • The current instance, its ancestor instances, and live spawned child instances.
  • Accessible content documents declared by subject, doc.ref, and doc.refs fields at workflow or current-stage scope.
  • The release document declared by a release.ref field.

Missing or unreadable documents are absent, and the engine does not follow references transitively. Singular reference fields resolve to hydrated documents in $fields, while doc.refs remains an array of reference identities. See Fields and Global document references for those contracts.

Reuse named predicates

When several decisions use the same instance rule, declare it once in predicates and reference it as $name. A named predicate can read instance variables, but not caller-bound variables or another named predicate.

defineWorkflow({
  // ...
  predicates: {
    readyToPublish: '$fields.approved == true && $allActivitiesDone',
  },
  stages: [
    defineStage({
      name: 'review',
      transitions: [
        defineTransition({
          name: 'publish',
          to: 'published',
          when: '$readyToPublish',
        }),
      ],
    }),
  ],
})

Start filters and requirements

Starting has two decisions. start.filter controls discovery. start.requirements is an ordered list of gates enforced before a fresh standalone start. See the start block reference for the complete nested interfaces.

A subject field identifies the document the workflow is about. See Field types for its declaration contract.

defineWorkflow({
  name: 'article-review',
  title: 'Article review',
  initialStage: 'review',
  fields: [
    defineField({
      type: 'subject',
      name: 'subject',
      types: ['article'],
      initialValue: {type: 'input'},
      required: true,
    }),
    defineField({
      type: 'boolean',
      name: 'approved',
      initialValue: {type: 'input'},
    }),
  ],
  start: {
    filter: "status == 'draft'",
    requirements: [
      {
        type: 'singleSubject',
        name: 'one-open-review',
        title: 'Review already in progress',
      },
      {
        type: 'groq',
        name: 'approved',
        title: 'Approval required',
        query: '$fields.approved == true',
      },
    ],
  },
  stages: [defineStage({name: 'review'})],
})

The subject field makes this workflow available for articles, and the filter narrows discovery to drafts. A fresh startInstance call succeeds only when no unfinished run of this definition holds the same subject and the supplied approved input is true. Resuming an unfinished start and parent-owned spawning do not evaluate start requirements.

Allow one active workflow per subject

Editorial Workflows identifies each subject with a global document reference, which combines the document ID with its dataset, project, or other Sanity resource. The full identity prevents unrelated documents with the same ID from blocking each other.

Use a singleSubject requirement when this definition may have only one unfinished run for the same subject. The rule applies across deployed versions of that definition. A different definition does not block it.

start: {
  requirements: [
    {
      type: 'singleSubject',
      name: 'one-open-review',
      description: 'Finish the existing review before starting another.',
    },
  ],
}

The workflow must declare one first-class subject field with initialValue: {type: 'input'}. Completing the existing run permits another start.

Query the raw snapshot

The * operator searches this snapshot; it does not open an unrestricted Content Lake query. Use the variables in the reference below when they expose the state you need. Use the raw instance only for state those variables do not expose, such as stage-visit history:

defineTransition({
  name: 'finish-after-rework',
  to: 'done',

  // $stage == "review" is true on every visit.
  // Read the raw stage entries to detect a return visit.
  when:
    'count(*[_id == $self][0].stages[name == "review"]) > 1',
})

The engine provides $stage, the current stage name. It does not provide a $stages history variable. The current-stage check is true on both the first and later review visits, while the $self query counts the persisted stage entries and distinguishes a return visit.

Building a custom interface or diagnostic? See Evaluation insights to explain the outcome of these conditions without changing how you compose them.

Reference

Not every condition site receives the same variables. The reference below lists the context available at each site.

Instance variables

These variables describe the running instance and are available at instance condition sites.

  • $fields

    Rendered field values

    Activity fields overlay stage fields, which overlay workflow fields. References hydrate only when their documents are in the evaluation snapshot.

  • $stage

    Current stage name

    Identifies the instance’s current stage.

  • $activities

    Current activity rows

    Includes each activity’s current status.

  • True when every current activity is done or skipped.

  • True when any current activity has failed.

  • $effects

    Completed effect outputs

    Contains declared outputs from completed effects.

  • $effectStatus

    Effect statuses

    Reports done or failed for effects queued during the current stage visit.

  • $subworkflows

    Child workflow registry

    Faceted by activity, definition, and status.

  • $self

    Current instance GDR URI

    Identifies the workflow instance document.

  • $parent

    Parent instance

    Identifies the parent instance, or null for a root instance.

  • $ancestors

    Ancestor instances

    Lists ancestor instances in root-first order.

  • $now

    ISO timestamp

    The clock value for the current evaluation.

  • $context

    Start context

    The context supplied when the instance started.

Context-specific variables

These variables appear only at the sites that supply their context.

  • $actor

    Actor identity

    Identifies the caller at caller-bound sites.

  • $assigned

    boolean

    True when the caller matches the activity assignment at caller-bound sites.

  • $can

    Permission values

    Contains advisory permission values resolved for the caller.

  • $params

    Action parameters

    Available to expressions carried by a caller-fired action.

  • $row

    Current row

    Available to row-oriented expressions such as operation where and spawn with.

Instance decision sites

These conditions make decisions about a running instance.

  • Transition when

    Instance variables

    Selects the first eligible outgoing transition in declaration order.

  • Activity filter

    Instance variables

    Determines whether the activity belongs to the current stage visit.

  • Action when

    Instance variables

    Triggers the action automatically when the condition is true.

  • Action filter, requirements, and editable predicates

    Instance variables plus caller context

    Controls caller-facing availability. These checks are advisory, not a security boundary.

Action expression sites

These expressions shape work after an action fires.

  • Effect input bindings

    Action context

    Resolve the input passed to an effect. Caller-fired actions may bind $params.

  • Operation where

    Action context plus $row

    Tests each stored array row before applying the operation.

  • Spawn forEach

    Workflow field context

    Selects the rows used to create child instances.

  • Spawn context

    Action context

    Constructs the start context for each child instance.

  • Spawn with

    Action context plus $row

    Constructs child inputs from the current row. Spawn expressions do not bind $can or $params.

Start decision sites

Start visibility and start permission use different inputs.

  • start.filter

    Candidate document context

    Controls discovery. It can read the candidate document, $tag, $definition, and $now. It cannot read $fields and does not gate startInstance.

  • start.requirements[]

    Start input context

    Ordered creation gates. GROQ requirements can read $tag, $definition, $now, and raw input $fields; singleSubject enforces definition-scoped subject exclusivity.

Guard predicates

Guard predicates use Content Lake delta GROQ with their own variables and enforcement model.

Visiting agent?

Was this page helpful?