Editorial Workflows

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: subject identifies 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: date stores YYYY-MM-DD; datetime stores an ISO-8601 timestamp. Field names are arbitrary; dueDate and dueDatetime are conventions. todoList specifically includes a nested dueDate.
  • Progress: progress stores completion from 0 through 100. Studio renders it as progress; effect handlers can report it with ctx.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},
})
  • string and text: min and max are inclusive character counts.
  • number: min and max are inclusive numeric bounds.
  • progress: intrinsically 0–100; min and max may only narrow that range.
  • Either bound may be omitted; min must not exceed max.
  • 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. required separately 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.

  • 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; accepts type, name, title, description, options, validation, fields, and of.

  • of

    FieldShape[]

    Member shape for an array; accepts the same FieldShape keys as fields.

  • 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, and doc.refs.

  • options

    ChoiceOptions

    Closed choice list for supported scalar types.

  • validation

    ScalarValidation

    Inclusive character-count bounds for string and text, numeric bounds for number, or narrower 0–100 bounds for progress.

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 query when 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 actor working-memory field. Declare a matching defineAction({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?}. dueDate is an optional date-only YYYY-MM-DD value used by Studio’s due-date and overdue controls.

  • notes

    array field

    Creates array rows shaped as {body, actor, at}. The names match the audit operation’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, and datetime fields. 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 types restricts accepted document types.

  • doc.ref

    GlobalDocumentReference | null

    One resource-qualified document reference. Optional types restricts accepted document types.

  • doc.refs

    GlobalDocumentReference[]

    An array of resource-qualified document references. Optional types applies 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; dueDate is a conventional name, not a separate type. todoList includes a nested dueDate field.

  • datetime

    ISO-8601 string | null

    An ISO-8601 timestamp. Studio gives any editable datetime field a date-and-time picker; dueDatetime is 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?

Was this page helpful?