Workflow authoring reference
Reference for Sanity Workflows authoring declarations, condition variables, decision sites, operations, guards, and subworkflow spawns.
This page lists every property the define* helpers in @sanity/workflow-engine/define accept, with the GROQ variables each condition site can read. For full TypeScript signatures, see @sanity/workflow-engine/define on reference.sanity.io. For how definitions behave, see Definitions and instances. For the stored instance document and evaluation output, see the Engine API reference.
A minimal definition with one working stage, one activity, one action, and one transition:
import {
defineAction,
defineActivity,
defineField,
defineStage,
defineTransition,
defineWorkflow,
} from '@sanity/workflow-engine/define'
export const articleReview = defineWorkflow({
name: 'article-review',
title: 'Article review',
initialStage: 'drafting',
fields: [defineField({type: 'subject', name: 'subject', required: true, initialValue: {type: 'input'}})],
stages: [
defineStage({
name: 'drafting',
activities: [
defineActivity({
name: 'write',
actions: [defineAction({name: 'submit', status: 'done'})],
}),
],
transitions: [defineTransition({name: 'to-approved', to: 'approved'})],
}),
defineStage({name: 'approved'}),
],
})Workflow definition
defineWorkflow(...) validates the definition, expands authoring shorthand, and returns the definition you deploy. If validation fails, it throws one error that lists every issue with its path. One of the checks is that every variable a condition reads is available where the condition runs. See Condition variables.
Definition properties
Requiredname
string
Required. Stable identity used in deployed document IDs and subworkflow references. Lowercase letters, digits, and dashes, starting with a letter or digit.
title
string
Optional. Non-empty display name. When omitted, the definition stores its name as the title, so every deployed definition has one. Omit it when it would equal the name.
description
string
Optional. Display explanation.
semantics
Semantic[]
Optional. Advisory meaning that tooling can read and execution ignores:
signal.positive,signal.caution,signal.critical, orcustom.<camelCase>, at most one per namespace.RequiredinitialStage
string
Required. The stage entered when an instance starts. Must name a declared stage.
Requiredstages
Stage[]
Required. One or more stage declarations.
fields
FieldEntry[]
Optional. Workflow-scope fields that live for the instance lifetime.
groups
Group[]
Optional. Presentation groups that fields, activities, and actions may join with
group. Each is{name, title?, description?, kind?};kindis'core'or'details'. Advisory: the engine validates names and never acts on groups.predicates
Record<string, string>
Optional. Named conditions, each pre-evaluated and bound as the boolean
$name. A predicate can read the variables marked always in the variables table, not caller variables or another predicate, and cannot reuse a built-in variable name.roleAliases
Record<string, string[]>
Optional. Roles that fulfill each authored role. Aliases widen action
roles,$assigned, and assignment eligibility; they never change an actor's roles. The"*"key lists universal fulfillers.lifecycle
'standalone' | 'child'
Optional. Default
'standalone'.'child'marks a spawn-only definition. Advisory: start pickers hide child definitions, butstartInstancedoesn't refuse one. A child definition can't declarestart.start
AuthoringStartBlock
Optional. Discovery and readiness rules for fresh standalone starts. See the start block section.
abort
{roles: string[]}
Optional. Limits who may call
abortInstance, withroleAliasesapplied. A caller it doesn't admit getsAbortDeniedError, administrators included. Omit it to let anyone who can write the instance abort it. Subworkflows stopped by their parent and instances aborted bydeleteDefinitionare not checked. RequiresexpectedMinReaderModel11.runtime
{kind: 'function' | 'durableFunction' | 'selfHosted'}
Optional. Authoring-only hosting hint for the generated runtime, overriding the deployment's kind. Deploy strips it.
Reader-model requirements such as the one on abort are explained in Upgrade packages. For runtime, see Runtime hosting configuration.
defineWorkflow({
name: 'article-review',
title: 'Article review',
initialStage: 'drafting',
predicates: {legalCleared: '$fields.legalApproved == true'},
roleAliases: {editor: ['administrator']},
abort: {roles: ['editor']},
// fields and stages
})Start block
The optional start block controls where a definition is offered for new runs and checks each new standalone start. It doesn’t run again when a start is resumed or when a parent spawns a child. For how it behaves, see Start filters and requirements.
start: {
kind: 'interactive',
filter: "status == 'draft'",
requirements: [
{
type: 'singleSubject',
name: 'one-open-review',
title: 'Review already in progress',
},
{
type: 'groq',
name: 'article-ready',
title: 'Article is not ready',
query: '$fields.ready == true',
},
],
}kind
'interactive' | 'autonomous'
Optional. Default
'interactive'. Classifies who normally starts the workflow. It doesn't gatestartInstance.filter
string
Optional. GROQ visibility predicate evaluated against a candidate document during discovery. It doesn't gate
startInstance. Root document reads require asubjectfield, including implicit reads throughreferences(),sanity::versionOf(), andsanity::partOfRelease(). Use$nowfor time; clock functions are rejected.requirements
StartRequirement[]
Optional. Ordered named checks enforced before a fresh standalone start. Every one must pass.
Start requirements
Each entry in start.requirements has a type:
groqruns a GROQ condition against the values supplied at start.singleSubjectallows at most one unfinished run of this definition per subject, across all deployed versions. It needs a workflowsubjectfield withinitialValue: {type: 'input'}. See Allow one active workflow per subject.
Requiredtype
'groq' | 'singleSubject'
Required. Selects the check.
Requiredname
string
Required. Unique within the requirements array. Verdicts identify the requirement by this name.
title
string
Optional. Editor-facing label.
description
string
Optional. Editor-facing explanation.
query
string
Required when
typeis'groq'. Condition over the start input context. It can't read a root document, including through implicit document functions or escaped projection scopes.$fieldsholds raw inputs, so document references are{id, type}. Use$nowordateTime($now)instead of clock functions.
Stage declaration
Pass this object to defineStage(...) in a workflow’s stages array. A stage with no transitions is terminal. Entering a terminal stage completes the instance, so a terminal stage can’t declare activities.
Requiredname
string
Required. Stable identity inside the definition.
title
string
Optional. Display label.
description
string
Optional. Display explanation.
semantics
Semantic[]
Optional. Advisory meaning that tooling can read and execution ignores:
signal.positive,signal.caution,signal.critical, orcustom.<camelCase>, at most one per namespace.fields
FieldEntry[]
Optional. Fields resolved on entry and kept with this stage visit.
activities
Activity[]
Optional. Units of work for the stage. Not allowed on a terminal stage.
transitions
Transition[]
Optional. Ordered routes to other stages. Omit them to make the stage terminal.
guards
Guard[]
Optional. Content mutation guards registered on entry and retracted on exit.
groups
Group[]
Optional. Presentation groups local to the stage.
editable
Record<string, true | string | string[]>
Optional. Tighten-only override keyed by in-scope field name. Each entry is ANDed with the field's own
editable, so a stage can narrow editing while it is current but never open a field the baseline leaves closed. Each key must name a field of this stage that declareseditable.
defineStage({
name: 'review',
editable: {legalApproved: ['legal']},
activities: [/* ... */],
transitions: [defineTransition({name: 'to-approved', to: 'approved'})],
})Transition declaration
Pass this object to defineTransition(...) in a stage's transitions array. It declares a one-way route from that stage to another stage, without operations or effects.
Requiredname
string
Required. Stable identity within the source stage.
title
string
Optional. Display label.
description
string
Optional. Display explanation.
Requiredto
string
Required. Name of the destination stage.
when
string
Optional. Default
$allActivitiesDone; use'true'for an unconditional route. Transitions are checked in declaration order and the firsttruefires. Afalseresult moves on to the next transition; an unevaluable result, such as GROQ null, stops selection.
defineTransition({
name: 'to-approved',
to: 'approved',
when: '$allActivitiesDone && $fields.legalApproved == true',
})Field declaration
Pass this object to defineField(...) in the fields array of a workflow, a stage, or an activity. Where you declare the field sets its scope:
- Workflow fields get their values when the instance starts, and last for the whole instance.
- Stage and activity fields get their values each time the instance enters the stage. They belong to that stage visit, or to that activity.
Conditions read the fields in scope as $fields. If two scopes declare a field with the same name, the narrower one wins: activity, then stage, then workflow.
Operations and field edits that leave out scope find the field the same way, starting from the nearest declaration. In an effect handler, on an instance started from a definition deployed at model 12 or later, the search starts from the activity whose action queued the effect. That applies to a bare setProgress field name and to field reads in an effect’s operations. On an instance started from an earlier definition, a bare setProgress name writes the workflow field, and those reads find the stage field, then the workflow field. Initial values that read another field follow their own rules; see the fieldRead row.
Requiredname
string
Required. Field name, read as
$fields.<name>. Letters, digits, and underscores, not starting with a digit.Requiredtype
string
Required. A field type or an authoring convenience.
title
string
Optional. Display label.
description
string
Optional. Display explanation.
group
string | string[]
Optional. Group or groups to join. The stored definition normalizes membership to
string[].initialValue
FieldSource
Optional. Source resolved when the field enters scope. Omit it to start empty (
nullor[]) and let operations fill the field.editable
true | string | string[]
Optional. Default: not editable.
trueopens direct editing to anyone in the field's scope window, a string is a condition, and a role list desugars to a role-membership condition. Advisory; a guard declares a write lock.required
boolean
Optional. Default
false. Valid only on workflow-scope fields withinitialValue: {type: 'input'}. Requires a non-null value at start or spawn. Onsubject,doc.ref, anddoc.refs, in an instance started from a definition deployed at model 10 or later, selected documents must also stay readable in the workflow perspective, and a missing target blocks actions and transitions. An instance started from an earlier definition checks the value only at start or spawn.types
string[]
Optional. Accepted document types, on
subject,doc.ref, anddoc.refsonly. Omit it to accept any type, except that asubjectwithouttypesexcludessanity.*andsystem.*types. List a reserved type to accept it. Engine-owned types are never valid subjects.roles
string[]
Optional. Non-empty project roles that new assignment members must fulfill. Valid on
assigneeandassigneesonly. RequiresexpectedMinReaderModel8, or 9 on anassigneefield.options
{list: Array<{title: string, value: string | number}>}
Optional. Closed choice list for
string,text,number,url,date,dueDate,datetime, anddueDatetime. Non-empty, with unique values and non-empty titles. Stored non-null values must equal one listed value.validation
{min?: number, max?: number}
Optional. Inclusive bounds: character counts for
stringandtext, values fornumber, and a narrower range inside 0 to 100 forprogress. Declare at least one bound. Other types rejectvalidation.fields
FieldShape[]
Required on
object, invalid elsewhere. Nested shapes, each acceptingtype,name,title,description,options,validation,roles,fields, andof. Nested shapes usedateanddatetime, notdueDateordueDatetime.of
FieldShape[]
Required on
array, invalid elsewhere. Member shape, with the same keys asfields.
A field with an options.list follows these rules:
- The list must not be empty. Values must be unique, titles must not be empty, and every value must match the field type and its validation bounds.
- Every write is checked against the list: initial values, edits, operations, action params, and effect outputs.
Validation bounds (min and max) are inclusive. You can leave out either one, and min can’t be greater than max.
The engine checks inputs, operations, action params, and effect outputs before it saves anything, so an invalid write stores nothing. A query-sourced value that fails validation is different: the field gets its empty value, and the engine records a fieldQueryDiscarded history entry. In Sanity Studio, an invalid input shows an inline error and blocks the start or action until it’s fixed.
For roles and other reader-model requirements, see Upgrade packages. For routing, eligibility, and Studio controls, see Configure fields.
defineField({
type: 'string',
name: 'priority',
editable: ['editor'],
initialValue: {type: 'literal', value: 'normal'},
options: {list: [{title: 'Normal', value: 'normal'}, {title: 'Urgent', value: 'urgent'}]},
})Field types
Canonical values accepted by a field declaration’s type.
subject
GlobalDocumentReference | null
The workflow's primary document. Workflow scope only; at most one. Document-aware integrations use it to discover the workflow for that document. Without
types, accepts ordinary content types and excludes reserved namespaces. Explicittypesmay include reserved types, but never engine-owned documents.doc.ref
GlobalDocumentReference | null
One resource-qualified document reference. Optional
typesrestricts accepted document types.doc.refs
GlobalDocumentReference[]
An array of resource-qualified document references. Optional
typesapplies to every member.release.ref
ReleaseRef | null
A reference to a Content Release, including its release name.
string
string | null
A short string value.
text
string | null
A multiline string value.
number
number | null
A numeric value.
progress
number | null
Completion from 0 through 100; fractions are allowed. Studio renders it as progress, and an effect handler can update it with
ctx.setProgress(). Validation may narrow the range; choice options are not supported.boolean
boolean | null
A boolean value.
date
YYYY-MM-DD | null
A date-only YYYY-MM-DD value. Studio gives any editable date field a date picker. Use this kind for ordinary dates and for nested dates, including the dueDate value inside each todoList row.
dueDate
YYYY-MM-DD | null
The deadline for one workflow, stage, or activity scope. It stores and validates the same value as date, supports options, and uses the same Studio date controls. A scope may declare at most one dueDate or dueDatetime field. The field must be top-level and is optional unless required is set.
datetime
ISO-8601 string | null
An ISO-8601 timestamp. Studio gives any editable datetime field a date-and-time picker. Use this kind for ordinary timestamps and nested timestamp fields.
dueDatetime
ISO-8601 string | null
The deadline, including time, for one workflow, stage, or activity scope. It stores and validates the same value as datetime, supports options, and uses the same Studio date-and-time controls. A scope may declare at most one dueDate or dueDatetime field. The field must be top-level and is optional unless required is set.
url
string | null
A URL string.
actor
Actor | null
An actor identity and its roles, for recording who performed work.
assignee
Assignee[]
An ordered assignment list with at most one person and any number of role members. Empty is []. User IDs are account-global human sanityUserId values; robots cannot be assignment members.
assignees
Assignee[]
An ordered list of people or collective project roles. User IDs are account-global human sanityUserId values; robots cannot be assignment members.
object
Record<string, unknown> | null
A compositional object. Declare its nested shape with
fields.array
Record<string, unknown>[]
An array of compositional objects. Declare the member shape with
of.
A subject field without types accepts any ordinary content type. It excludes types whose names start with sanity. or system.. To accept one of those, list it in types.
This default applies to older stored definitions too. It only affects where a definition is offered. A direct startInstance call doesn’t check the document type of a subject that has no types.
You can’t list a document type the engine owns (sanity.workflow.instance, sanity.workflow.definition, or temp.system.guard) in subject.types. defineWorkflow and deploy both reject it. To coordinate workflow instances, use spawn.
dueDate and dueDatetime aren’t valid inside object or array fields, or as effect outputs. Use date or datetime there. In a todoList row, dueDate is a property name, and its type is date. Both due-date types belong to data model 5 and don’t raise the reader floor, but an older engine can still reject a type it doesn’t know.
Initial-value sources
Set one of these objects as a field's initialValue. The engine resolves it once, when the field enters scope. A source doesn't make the field editable.
input
{type: 'input'}
Takes a workflow-scope value supplied at start or by a parent's spawn. Sanity Studio shows a start control for each unmapped workflow input. Stage and activity input seeds receive no value, and an activity input seed produces a deploy warning.
literal
{type: 'literal', value}
Uses a fixed value authored in the definition.
query
{type: 'query', query}
Runs GROQ against the Content Lake when the field enters scope, with earlier fields in that scope as
$fields. Reference normalization can drop unrecognized values. A normalized result that fails validation stores the field's empty value and recordsfieldQueryDiscarded. A failed query throws.fieldRead
{type: 'fieldRead', field, scope?, path?}
Copies an earlier field in the same scope, optionally selecting
path. A stage or activity seed can setscope: 'workflow'to read workflow fields; an activity seed can't read stage fields. Doesn't load referenced documents.
Authoring conveniences
Use these names as the type in defineField(...). defineWorkflow expands each one into one or more standard fields.
todoList
array field
An
arrayof rows shaped{label, status, assignee?, dueDate?}:labelandstatusare strings,assigneeis anassigneemember list, anddueDateis adatethat Studio deadline controls use.notes
array field
An
arrayof rows shaped{body, actor, at}(text,actor,datetime). The names match the stamps theauditoperation adds, soauditrows fit. Append-only is a convention, not an enforced type.
Activities and actions
An activity is a unit of work in a stage. Actions on the activity carry every operation, effect, and spawn.
Activity declaration
This is the object passed to defineActivity(...) in a stage’s activities array.
Requiredname
string
Required. Stable activity name.
title
string
Optional. Display label.
description
string
Optional. Display explanation.
semantics
Semantic[]
Optional. Advisory meaning that tooling can read and execution ignores:
signal.positive,signal.caution,signal.critical, orcustom.<camelCase>, at most one per namespace.groups
Group[]
Optional. Groups declared for this activity's fields and actions.
group
string | string[]
Optional. Groups this activity joins.
target
{type: 'url', url} | {type: 'field', field}
Optional. Deep link for work done outside Workflows.
urlmust be http or https.fieldnames adoc.ref,doc.refs,subject, orrelease.reffield reachable from the activity. Render-only; never gates.filter
string
Optional. Existence condition evaluated once at stage entry. A definite
falserecords the activity asskipped, hides it, and keeps it from blocking$allActivitiesDone.requirements
GroqRequirement[]
Optional. Ordered named readiness checks, each
{type: 'groq', name, title?, description?, query}. An unmet one keeps the activity visible and disables its caller-fired actions withrequirements-unmet.actions
Action[]
Optional. Actions that resolve the work or change instance state.
fields
FieldEntry[]
Optional. Activity-scope fields resolved at stage entry.
defineActivity({
name: 'legal-check',
filter: '$fields.needsLegal == true',
requirements: [{type: 'groq', name: 'assigned', title: 'Only the assignee can sign off', query: '$assigned'}],
fields: [defineField({type: 'assignees', name: 'assignees', editable: true})],
actions: [defineAction({name: 'clear', status: 'done'})],
})Action declaration
Pass this object to defineAction(...) in an activity’s actions array. How the action fires depends on when:
- Without
when, a caller fires the action withfireAction, and a UI can show it as a button. - With
when, the engine fires the action automatically when the condition istrue.
Requiredname
string
Required. Stable action name.
title
string
Optional. Display label.
description
string
Optional. Display explanation.
semantics
ActionSemantic[]
Optional. Advisory meaning:
decision.accept,decision.decline,signal.positive,signal.caution,signal.critical, orcustom.<camelCase>, at most one per namespace. Decision values are action-only. Execution ignores semantics.group
string | string[]
Optional. Groups this action joins.
when
string
Optional. When present, the engine fires the action as soon as the condition turns true, at most once per stage visit, and
fireActionrejects it. Use'true'to fire on stage entry.filter
string
Optional. Existence condition. A non-matching action is hidden, not disabled. With
when,filterdecides whether the automation exists andwhentriggers it.roles
string[]
Optional. On a caller-fired action, desugars to a role-membership condition ANDed with
filter. On an action withwhen, it is stored as is and limits which identities may run the trigger.roleAliasesapplies either way.params
ActionParam[]
Optional. Caller-supplied values, each
{name, type, title?, description?, required?, options?, validation?}.typeisstring,number,boolean,url,dateTime,actor,doc.ref,doc.refs, orjson. A missing required param throwsActionParamsInvalidErrorand nothing commits. The caller’s args are JSON values.hasOwnPropertyis a reserved param name:defineWorkflowrejects an action that declares it.fireActionrejects caller args with ahasOwnPropertykey that the action doesn’t declare, withActionParamsInvalidError, and commits nothing. An instance whose definition declares ahasOwnPropertyparam and was deployed with Workflows 0.37 or earlier accepts that param like any declared one.ops
Operation[]
Optional. Instance mutations committed with the firing.
effects
Effect[]
Optional. External work queued by the firing.
spawn
Subworkflows
Optional. Child workflows created by the firing.
status
'done' | 'skipped' | 'failed'
Optional. Shorthand for a final
status.seton the firing activity, appended afterops. Without it, the activity stays active.
defineAction({
name: 'escalate',
when: "dateTime($now) > dateTime($fields.dueDate) + 60*60*24",
roles: ['editor'],
ops: [defineOp({type: 'field.set', target: {field: 'escalated'}, value: {type: 'literal', value: true}})],
})Effect declaration
Pass this object to defineEffect(...) in an action's effects array. The engine queues it when the action commits, and a handler registered under its name runs it. The Engine API reference lists every property, including bindings, outputs, retry, and runtime.
defineAction({
name: 'notify',
when: 'true',
effects: [
defineEffect({
name: 'notify-reviewer',
bindings: {article: '$fields.subject._id'},
outputs: [{name: 'messageId', type: 'string'}],
}),
],
})Operations and value expressions
Operations change instance state in the same commit as the action that carries them.
Operation catalogue
Use these objects with defineOp(...) in an action’s ops array. An effect handler’s successful completion can also return the eight field.* operations directly.
field.set
Any field
Replaces the field value with a resolved value expression. Runtime-written references must target a declared resource.
field.setIfMissing
Nullable field
Sets a resolved value only when the current nullable value is absent or null. List fields are always arrays and are rejected.
field.inc
number
Adds a resolved finite numeric delta to the current value and rejects a non-finite result. The delta defaults to 1.
field.dec
number
Subtracts a resolved finite numeric delta from the current value and rejects a non-finite result. The delta defaults to 1.
field.unset
Any field
Clears the field value.
field.append
List field
Appends a resolved value to a list field. The value must match the declared member shape.
field.updateWhere
array
Merges an object into rows selected by
where. Valid only forarrayfields; merged rows must retain the declared shape, and the merge cannot write_keyor_type.field.removeWhere
Any list field
Removes rows selected by
where. Valid for every list field, includingdoc.refs.status.set
Activity
Sets an activity to
active,done,skipped, orfailed. Available to actions, not effect completions; it defaults to the firing activity but may name a sibling activity.audit
List field
Authoring shorthand for
field.appendthat addsactorandatstamps to anobjectvalue. Rename the stamps withstampFields: {actor?, at?}; a stamp that collides with an authored key is rejected.
Set one field from an action parameter and clear another:
defineAction({
name: 'change-deadline',
params: [{name: 'deadline', type: 'dateTime', required: true}],
ops: [
defineOp({
type: 'field.set',
target: {field: 'deadline'},
value: {type: 'param', param: 'deadline'},
}),
defineOp({type: 'field.unset', target: {field: 'reminderSentAt'}}),
],
})Seed a counter only when it is missing, then increment it:
defineAction({
name: 'retry',
ops: [
defineOp({
type: 'field.setIfMissing',
target: {field: 'attempts'},
value: {type: 'literal', value: 0},
}),
defineOp({type: 'field.inc', target: {field: 'attempts'}}),
],
})A field.setIfMissing that finds a value already there writes nothing, and records no opApplied history entry. field.inc and field.dec add or subtract each time they run, so a retried call counts twice. If a caller may retry, pass an idempotency key.
To retry an effect a fixed number of times, count attempts before each dispatch. Retry only while the count is below your limit, and send the last failure to manual recovery. For example, a definition with a limit of three makes three attempts in total and never queues a fourth.
field.inc, field.dec, and field.setIfMissing belong to data model 6. They don’t raise the reader floor, so an older engine still reads the instance, but it doesn’t recognize these operations. Before you deploy a definition that uses one, see Upgrade Workflows packages.
Append a row that records the note, the actor, and the time. The audit shorthand writes the same kind of stamped row:
defineAction({
name: 'add-note',
params: [{name: 'body', type: 'string', required: true}],
ops: [
defineOp({
type: 'field.append',
target: {field: 'notes'},
value: {
type: 'object',
fields: {
body: {type: 'param', param: 'body'},
actor: {type: 'actor'},
at: {type: 'now'},
},
},
}),
],
})Update and remove matching rows. where is evaluated once per row with $row bound to it:
defineAction({
name: 'complete-item',
params: [{name: 'itemKey', type: 'string', required: true}],
ops: [
defineOp({
type: 'field.updateWhere',
target: {field: 'todoList'},
where: '$row._key == $params.itemKey',
value: {
type: 'object',
fields: {
status: {type: 'literal', value: 'done'},
},
},
}),
defineOp({
type: 'field.removeWhere',
target: {field: 'watchers'},
where: '$row.id == $actor.id',
}),
],
})A successful effect completion returns the field.* forms directly. Each target names its workflow, stage, or activity scope, where activity is the activity whose action queued the effect, and status.set is not allowed:
return {
ops: [
{
type: 'field.set',
target: {scope: 'stage', field: 'generatedDocument'},
value: {type: 'literal', value: documentRef},
},
{
type: 'field.set',
target: {scope: 'activity', field: 'generationStatus'},
value: {type: 'literal', value: 'ready'},
},
],
}Value expressions
Set one of these serializable objects as an operation’s value. The engine resolves it when the operation executes.
literal
{type: 'literal', value}
Uses a fixed value authored in the definition or supplied by a trusted completion result.
fieldRead
{type: 'fieldRead', field, scope?, path?}
Reads a field or one of its nested paths. Without
scope, it searches activity, stage, then workflow fields. Doesn't fetch documents.param
{type: 'param', param}
Reads a parameter supplied to the firing action.
actor
{type: 'actor'}
Uses the acting identity.
now
{type: 'now'}
Uses the shared timestamp for the engine operation.
self
{type: 'self'}
Uses the current instance's global document reference.
stage
{type: 'stage'}
Uses the current stage name.
object
{type: 'object', fields}
Builds an object recursively from other value expressions.
Combine value expressions into one object. The action that carries this operation must declare a reason param:
const recordDecision = defineOp({
type: 'field.set',
target: {field: 'decision'},
value: {
type: 'object',
fields: {
approved: {type: 'fieldRead', field: 'approved'},
reason: {type: 'param', param: 'reason'},
decidedBy: {type: 'actor'},
decidedAt: {type: 'now'},
},
},
})Guard declaration
Pass this object to defineGuard(...) in a stage’s guards array. The guard is active while the instance is in that stage. A guard predicate isn’t a workflow condition. The Content Lake runs it in delta mode against each attempted change to a matched document. See Hold publishing with guards.
Requiredname
string
Required. Unique across the definition. Lowercase letters, digits, and dashes, starting with a letter or digit.
title
string
Optional. Display label.
description
string
Optional. Display explanation.
Requiredmatch
{actions, types?, idRefs?, idPatterns?}
Required.
actionslists at least one ofcreate,update,delete,publish,unpublish.typesintersects the ID criteria; matching eitheridRefs(typed reads) oridPatterns(resource-local IDs with*) is enough. Invalid patterns, and release-version patterns that would broaden to every document, are rejected before deploy.predicate
string
Optional. Delta-mode GROQ over
document.before,document.after,mutation.action, andguard.metadata, withidentity()and->on stored resource-local references. Only a stricttrueallows a matching mutation. If you omit `predicate`, every mutation that `match` selects is denied.metadata
Record<string, GuardRead>
Optional. Values resolved when the guard is created or refreshed and exposed as
guard.metadata. A read is{type: 'self'},{type: 'now'},{type: 'fieldRead', field, path?}(workflow fields only), or{type: 'effectsRead', effect, path?}.
defineGuard({
name: 'freeze-copy',
match: {idRefs: [{type: 'fieldRead', field: 'subject'}], actions: ['update']},
predicate: '!delta::changedAny((title, body))',
})Subworkflow spawn declaration
Set this object as an action’s spawn property. When that action fires, the engine creates the declared child workflow instances in the same commit.
RequiredforEach
string
Required. GROQ producing one row per child. Evaluated with workflow-scope
$fieldsonly and no caller variables or named predicates. Each row needs a unique identity:_key, then_id, then a GDR'sid, or a scalar's string form. Missing or duplicate identities reject the spawn.Requireddefinition
{name: string, version?: number | 'latest'}
Required. Deployed child definition.
versiondefaults to the latest.with
Record<string, string>
Optional. Child input-field values, each evaluated per row with
$row.context
Record<string, string>
Optional. Values evaluated once, without
$row, and delivered to each child as$context. Results must be strings, numbers, booleans, or full GDRs;nullomits the entry, and other objects reject the spawn.onExit
'detach' | 'abort'
Optional. Default
'detach', which leaves live children running when their cohort leaves scope.'abort'stops them recursively. Aborting the parent always stops its children.
defineAction({
name: 'translate',
when: 'true',
spawn: {
forEach: '*[_id == $fields.subject._id][0].locales',
definition: {name: 'translate-article'},
with: {locale: '$row'},
},
})Cohort row
Each element of $subworkflows has these properties. stage is null until the child instance has been created and loaded.
interface SubworkflowVarRow {
_id: string
rowKey: string
activity: string
action: string
definition: string
current: boolean
stage: string | null
status: 'active' | 'done' | 'aborted'
spawnedAt: string
}Condition variables
Conditions are GROQ strings. Each place a condition runs, called a site, provides a fixed set of variables. defineWorkflow and deploy reject a condition that reads a variable its site doesn’t provide. For why the sites differ, see Caller-bound and caller-blind sites.
defineWorkflow, validateDefinition, and deploy also reject a reference that can’t resolve where it’s read. Each error names the site. When a valid name is close, the error suggests it, for example reads $feilds, which this scope does not bind (did you mean $fields?). These references are rejected:
- A
$<name>that the site doesn’t provide and that isn’t a declared predicate. This applies at every condition site, including predicate bodies,spawnexpressions,start.filter, startgroqrequirements, and theabort.filterof a stored definition. - A named predicate read in
spawn.forEach, which provides no predicates. Also a predicate body that reads its own name. - A param the action doesn’t declare, read in a caller-fired action’s effect bindings or operation
whereas$params.<name>, or as a{type: 'param'}value.fireActionpasses undeclared params through without checking them, so declare every param the action reads. - An action that declares a param named
hasOwnProperty. GROQ reads every$paramsvalue through that method, so the name is reserved.fireActionalso rejects ahasOwnPropertyarg that the action doesn’t declare. - A
{type: 'param'}value on an action withwhen. An automatic action has no caller, so it has no params.
Variables
The Binding column says when a variable has a value:
- always: at every site that evaluates an instance.
- caller: only when someone, such as an editor or a service, is acting. These sites are called caller-bound.
- row: only for one row at a time, in
spawn.withor an operationwhere. - start: only in the
startblock.
Variable | Type | Binding | Meaning |
|---|---|---|---|
|
| always | GDR URI of the instance document. |
|
| always | Field values by name; activity fields overlay stage fields, which overlay workflow fields. A |
|
| always | Parent instance GDR URI, |
|
| always | Ancestor instance GDR URIs, root first. |
|
| always | Current stage name. |
|
| always | ISO timestamp shared by every condition in one evaluation pass. |
|
| always | Start context by entry name: values seeded at |
|
| always | Completed effect outputs, |
|
| always |
|
|
| always | Current stage activity rows, with status. |
|
| always | Every current activity is |
|
| always | Some current activity is |
|
| always | Every spawned child, as cohort rows. Settled gate: |
|
| always | Each named predicate from |
|
| caller | Acting identity, |
|
| caller | The caller matches the activity's |
|
| caller | Advisory per-permission booleans from the caller's grants; |
|
| caller | Advisory organization-level User Attributes (Enterprise), the values |
|
| caller | The fired action's arguments. Bound only while that action's effect bindings and operation |
|
| row | One |
|
| start | The engine's partition. In a start filter or start requirement, |
|
| start | Name of the definition being evaluated. |
Condition sites
Each row lists the variables a site provides on top of the always set. All always means every always variable, including named predicates.
Site | Binds | Decides |
|---|---|---|
Transition | All always | Picks the first route whose condition is |
Activity | All always | Whether the activity exists in this stage visit. Evaluated once at entry. |
Action | All always | Fires the action automatically when |
Action | All always | Whether the automation exists. |
Action | All always, | Whether the action exists for this caller. Advisory. |
Activity | All always, | Whether the activity's caller-fired actions are enabled. Advisory. |
Field | All always, | Who may edit the field. Advisory. |
Effect | All always, | Values passed to the effect handler. |
Effect | All always, | Values passed to the effect handler. |
Operation |
| Which rows the operation changes. An unevaluable row never matches. No other variable, and no named predicate. |
Operation | Same as caller-fired, without | Which rows the operation changes. |
| Always set except named predicates; | Which children to create. |
| All always, | Each child's input values. |
| All always, | The children's |
Named predicate ( | Always set except other predicates | A reusable boolean, |
|
| Whether a start surface offers the definition for a document. Never gates |
|
| Whether a fresh standalone start may proceed. |
Guard |
| Whether Content Lake allows the mutation. |
In start.filter and in a start requirement, * scans a reduced view of the instances in the engine’s partition. Each row has only definition, subject, and completedAt, and the scan includes completed and aborted instances.
Guard predicates
Guard predicates are not conditions. They read document.before, document.after, mutation.action, and guard.metadata; see the guard declaration and Hold publishing with guards.
Visiting agent?
Workflows has MCP tools that let an agent inspect, operate, author, validate, and deploy workflows. An agent host embeds these tools. Ask the person you work for whether your host includes the Workflows tools.