Fields
Fields carry the typed data belonging to a workflow instance.
Fields use the same basic model as fields in a Sanity schema: each declaration has a name, a type, and optional presentation or validation settings. The difference is where the value lives: Editorial Workflow fields are stored on the workflow instance, and their declaration location determines their lifetime and where they can be read.
Document-valued fields use global document references. These references preserve the document’s project, dataset, or other Sanity resource.
Field scopes
Fields may be declared at three scopes:
- Workflow fields resolve when the instance starts and persist for its lifetime.
- Stage fields resolve on stage entry and belong to that stage visit.
- Activity fields resolve on stage entry and belong to one activity.
assignee and assignees assign an activity only at activity scope. At workflow or stage scope they are ordinary data. Conditions explains how $assigned evaluates those assignments.
A reference may name its scope explicitly. When authoring omits the scope, lookup is lexical: activity, then stage, then workflow. Stored definitions carry an explicit resolved scope.
At evaluation time, $fields is the rendered cascade for the current location. A narrower field with the same name shadows a wider one.
Actions change fields through operations. The engine exposes the values to conditions as $fields; changing one can reveal an action or make a transition fire.
Common field types
Every field declares a type that determines the value it stores and any specialized behavior. The examples below recur in many workflows. For the complete list, see Field types.
- Subject:
subjectidentifies the workflow’s primary document. It is workflow-scoped, unique, and commonly supplied when the instance starts. Studio also uses its accepted document types to discover matching workflows. - Dates:
datestoresYYYY-MM-DD;datetimestores an ISO-8601 timestamp. Field names are arbitrary;dueDateanddueDatetimeare conventions.todoListspecifically includes a nesteddueDate. - Progress:
progressstores completion from 0 through 100. Studio renders it as progress; effect handlers can report it withctx.setProgress().
Declare each field at the scope that owns its value:
const subject = defineField({
type: 'subject',
name: 'subject',
initialValue: {type: 'input'},
required: true,
})
const dueDate = defineField({
type: 'date',
name: 'dueDate',
editable: true,
})
const dueDatetime = defineField({
type: 'datetime',
name: 'dueDatetime',
editable: true,
})
const generationProgress = defineField({
type: 'progress',
name: 'generationProgress',
})Fields in conditions
The engine does not expose raw stored field envelopes to conditions. Before evaluating a condition, it renders the fields in scope into the bespoke GROQ environment used by Editorial Workflows and binds the result as $fields.
Primitive fields become primitive values. Object and array fields keep their declared shape. A loaded subject or doc.ref is hydrated to the referenced document, so a condition can read through it; if that document is not in the evaluation snapshot, the value remains the bare global reference.
defineStage({
name: 'review',
fields: [
defineField({type: 'actor', name: 'reviewer'}),
defineField({type: 'boolean', name: 'approved'}),
],
activities: [
defineActivity({
name: 'approval',
actions: [
defineAction({
name: 'approve',
filter: '$actor.id == $fields.reviewer.id',
ops: [
defineOp({
type: 'field.set',
target: {field: 'approved'},
value: {type: 'literal', value: true},
}),
],
}),
],
}),
],
transitions: [
defineTransition({name: 'publish', to: 'published', when: '$fields.approved == true'}),
],
})Conditions are reevaluated after every committed engine operation and whenever a reactive session receives a relevant content change. A field update can therefore reveal an action, satisfy a requirement, fire a triggered action, or select a transition in the same cascade.
Use Conditions for the full expression grammar and the other variables available beside $fields.
When referenced content is deleted
Deleting a document referenced by a subject, doc.ref, or doc.refs field does not delete or terminate the Editorial Workflow instances that watch it. The application owns that lifecycle policy: retain the affected runs, or use a sanctioned engine verb such as abortInstance to stop them while preserving their audit history. See Handle a deleted subject document for a document-delete Function using instancesForDocument; never raw-patch or delete workflow instance documents.
Setting initial values
Without initialValue, a field is working memory: it starts empty and actions or direct edits may populate it.
An initial value declares how a field is populated when it enters scope. Use input for a value supplied at start, literal for a fixed value, query for a value computed from the Content Lake, or fieldRead for a value copied from an already-resolved field. See Initial-value sources for the complete forms.
Input values
Use input when a caller must supply the value as the field enters scope. At workflow scope, Sanity Studio renders a start-time control for every input not already supplied by the document mapping. A mapped document supplies the subject automatically; it does not suppress the other inputs. required is available only for input values.
defineField({
type: 'string',
name: 'priority',
initialValue: {type: 'input'},
required: true,
options: {
list: [
{title: 'Standard', value: 'standard'},
{title: 'Urgent', value: 'urgent'},
],
},
})Literal values
Use literal for a fixed value authored in the workflow definition. The engine validates the value against the field type when it resolves the field.
defineField({
type: 'string',
name: 'priority',
initialValue: {type: 'literal', value: 'standard'},
options: {
list: [
{title: 'Standard', value: 'standard'},
{title: 'Urgent', value: 'urgent'},
],
},
})Query values
Use query to evaluate GROQ against the Content Lake when the field enters scope. A query can use earlier, already-resolved fields in the same scope through $fields, so declaration order matters.
fields: [
defineField({
type: 'subject',
name: 'subject',
initialValue: {type: 'input'},
required: true,
}),
defineField({
type: 'string',
name: 'intakeRequestUrl',
initialValue: {
type: 'query',
query: '*[_id == $fields.subject.id][0].intakeRequestUrl',
},
}),
]Use a query when you need content from a referenced subject or document. If the result does not match the declared field type, the engine discards it and uses that field type’s default value.
Field-read values
Use fieldRead to copy the already-resolved value of another field. Lookup is lexical unless you set scope explicitly; path may select a nested value stored inside the source field.
fields: [
defineField({
type: 'string',
name: 'requestedBy',
initialValue: {type: 'input'},
required: true,
}),
defineField({
type: 'string',
name: 'reviewOwner',
initialValue: {type: 'fieldRead', field: 'requestedBy'},
}),
]A field read copies the stored field value; it does not fetch content from a subject or doc.ref. Use a query when you need a property from the referenced document.
Operations use a related value-expression grammar; see Operations.
Choice lists
Use options.list to define the complete allowed values for a scalar field. Each entry carries the label an interface presents and the stored string or number value. The list must be non-empty, values must be unique and match the field type, and every write path enforces membership. This includes initial values, edits, operations, action parameters, and effect outputs.
defineField({
type: 'string',
name: 'priority',
options: {
list: [
{title: 'Normal', value: 'normal'},
{title: 'Urgent', value: 'urgent'},
],
},
})Validation
Use validation on supported field types to narrow the values they accept:
defineField({
type: 'number',
name: 'wordCount',
validation: {min: 300, max: 2_000},
})stringandtext:minandmaxare inclusive character counts.number:minandmaxare inclusive numeric bounds.progress: intrinsically 0–100;minandmaxmay only narrow that range.- Either bound may be omitted;
minmust not exceedmax. - Engine: Checks non-null inputs, operations, action parameters, and effect outputs before commit. Invalid writes persist nothing. Invalid query results fall back to the field default and record a discard.
- Studio: Shows an inline error for editable fields and disables invalid start or action submission.
requiredseparately controls whether an initialization value must be present.
Editable fields
Fields are not directly editable by default. The editable property accepts three distinct forms: true allows any caller while the field is in scope; one string is always a GROQ condition; and a non-empty string[] is always a list of allowed role names. GROQ edit conditions can read $actor, $can, $fields, and $assigned.
Direct editing is still an engine operation: it evaluates the field’s editability, applies one transaction, records history, refreshes guards, and runs the cascade.
fields: [
// Anyone may edit while this field is in scope.
defineField({type: 'text', name: 'summary', editable: true}),
// A single string is a GROQ condition.
defineField({
type: 'text',
name: 'reviewNote',
editable: '$actor.id == $fields.reviewer.id',
}),
// A string array is role shorthand.
defineField({type: 'text', name: 'internalNote', editable: ['editor', 'admin']}),
]Reference
Field declaration
This is the object passed to defineField(...). Put field declarations in the fields array of a workflow, stage, or activity; that location determines the field’s scope.
name
string
Stable field name within its scope.
type
Field type
One stored field type or an authoring convenience.
title / description
string
Optional presentation metadata.
group
string | string[]
Optional presentation group or groups. Authoring accepts one name or a non-empty list; the stored definition normalizes membership to
string[].initialValue
InitialValue
Optional source resolved when the field enters scope.
editable
boolean | Condition | string[]
Enables direct editing, optionally gated by a condition or role shorthand.
fields
FieldShape[]
Nested shape for an
object; acceptstype,name,title,description,options,validation,fields, andof.of
FieldShape[]
Member shape for an
array; accepts the sameFieldShapekeys asfields.required
boolean
Requires a caller value. Valid only on workflow-scope fields with
initialValue: {type: 'input'}.types
string[]
Accepted document types for
subject,doc.ref, anddoc.refs.options
ChoiceOptions
Closed choice list for supported scalar types.
validation
ScalarValidation
Inclusive character-count bounds for
stringandtext, numeric bounds fornumber, or narrower 0–100 bounds forprogress.
Initial-value sources
Set one of these objects as a field declaration’s initialValue. The engine resolves it when that field enters scope. Definitions—not Studio mappings—declare which values are inputs, fixed defaults, or computed values.
input
{type: ‘input’}
Takes a value supplied when the workflow starts. In Sanity Studio, every workflow-scoped input not already supplied by the document mapping appears as a start-time control. Only input fields may set
required.literal
{type: ‘literal’, value}
Uses a fixed value authored in the definition.
query
{type: ‘query’, query}
Evaluates a GROQ expression against the Content Lake when the field enters scope. The query can read earlier, already-resolved fields in the same scope through
$fields. Use this source to read content from a referenced subject or document. If the result does not match the field type, the engine discards it and uses the field type’s default value.fieldRead
{type: ‘fieldRead’, field, scope?, path?}
Copies the already-resolved value of another field, or a nested path stored inside that value. It does not fetch the referenced document’s content. Use
querywhen you need a property from a subject or document reference.
Authoring conveniences
Use these names as the type in defineField(...). The definition compiler expands each convenience into canonical fields. A claim field must be paired with a separately declared claim action; deploy rejects either half when its pair is missing.
claim
actor working-memory field
Expands to an
actorworking-memory field. Declare a matchingdefineAction({type: 'claim', field: '<name>'}); the field sugar does not create the action.todoList
array field
Creates an array of todo rows shaped as
{label, status, assignee?, dueDate?}.dueDateis an optional date-onlyYYYY-MM-DDvalue used by Studio’s due-date and overdue controls.notes
array field
Creates array rows shaped as
{body, actor, at}. The names match theauditoperation’s body and automatic actor/time stamps; append-only behavior is a consumer convention, not a separate stored field type.
Options and validation
options
{list: Array<{title: string, value: string | number}>}
Optional closed choice list for
string,text,number,url,date, anddatetimefields. Stored non-null values must equal one listed value.validation
{min?: number, max?: number}
Optional inclusive character-count bounds for string and text, numeric bounds for number, or narrower 0–100 bounds for progress. Other field types do not accept validation.
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. Optional
typesrestricts accepted document types.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-DDvalue. Studio gives any editabledatefield a date picker;dueDateis a conventional name, not a separate type.todoListincludes a nesteddueDatefield.datetime
ISO-8601 string | null
An ISO-8601 timestamp. Studio gives any editable
datetimefield a date-and-time picker;dueDatetimeis only a conventional field name.url
string | null
A URL string.
actor
Actor | null
An actor identity and its roles; this is the value written by a claim pair.
assignee
Assignee | null
One user or role assignment. User IDs are account-global sanityUserId values.
assignees
Assignee[]
An array of user or role assignments. User IDs are account-global sanityUserId values.
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.
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.