Skip to main content

Where expressions appear

Three kinds of fields in the designer take dynamic values: The first two use the same formula language; body fields are plain prose with embedded {{ }} references.

The formula language

A formula is a single expression: references, literals, operators, and function calls.
References start with $ and use dotted paths — $instance.amount, $document.kw.Status, $object.attr.Total. Typing $ opens autocomplete with everything the workflow can see; a keyword or attribute whose name contains spaces uses the bracket form $[Invoice Number]. Hovering a reference shows its label and type. Keywords are lists (more below), so two suffixes do the common work: .Value is the first value, .Count is the number of values. Operators are the usual infix set — ==, !=, <, <=, >, >=, +, -, *, /, % — with and / or (or && / ||) as connectives and ! for negation. Comparisons are date-aware. Literals: numbers, "strings" (single or double quotes), true, false, null, arrays [1, 2, 3], and objects { key: expression }. Functions are called by their namespaced name, category.name(arguments) — the form autocomplete offers and the editor displays. The bare name (cat(…) for string.cat(…)) also parses, but the editor rewrites it to the namespaced form: Inside some / all / none, $item is the current element (and $item.Field a field of it). The editor gives live feedback as you type: syntax errors are underlined at the offending character, unknown references are flagged, and the expression is type-checked against what the field needs — a guard must produce a boolean, a number parameter a number.

What you can reference

The catalog depends on the workflow’s anchor and scope:
Instances start from events with an empty variable bag, so $instance.<name> only resolves after something in the workflow has set it. An unset variable resolves to null — a guard reading it will silently misfire. Read from the document or object when in doubt.

Keywords are lists — cardinality rules

The most common source of subtly wrong conditions. A standalone keyword is always a list, even when the keyword type holds a single value: Comparing the whole list to a single value ($document.kw.Status == "X") is flagged by validation — use .Value or array.in instead. Keyword groups follow the same pattern one level deeper: a single-instance group is a record whose fields are themselves lists ($document.group.Address.City.Value), and a multi-instance group is a list of records — test its instances with array.some($document.group.LineItems, $item.Amount.Value > 100). WorkView attributes are single-valued: compare them directly, no .Value. Keyword values are usually text; when comparing to a number, convert first: number.toNumber($document.kw.Amount.Value) >= 1000.

Trigger filters

A trigger filter runs before any instance exists, so it evaluates against the event rather than workflow state, and its references differ:
  • Document events expose $document.documentId, $document.documentTypeId, $document.documentTypeName, plus $document.keywords.<Name> and $document.groups.<Name> — note the namespace differs from the in-workflow $document.kw.<Name> form
  • WorkView events expose the event’s payload fields directly
  • $instance.* and $secrets.* are not available
To start a workflow on every in-scope event, simply omit the filter.

Body templates

Free-text fields — an email body, a note text — open the template editor: normal prose with references embedded as {{path}} (no $ prefix). Typing {{ opens the same reference autocomplete, names with spaces use {{[Invoice Number]}}, and hovering a token shows the reference’s label and type.
A body can also embed a whole computed expression with {{= … =}}, which holds JSONLogic (see below).

Under the hood: JSONLogic

Formulas are the editing surface; the stored form is JSONLogic. You meet it when editing a definition in the designer’s JSON view, or when generating definitions programmatically (for example with an AI agent):
  • Guards and trigger filters are stored as raw JSONLogic objects: { ">=": [ { "var": "instance.amount" }, 10000 ] }
  • Computed action parameters are stored as {{= JSONLogic =}} strings: "{{= { \"var\": \"instance.amount\" } =}}" — the body must be valid JSON, so a constant string keeps its quotes ({{= "Approved" =}})
  • References drop the $ ({"var": "instance.amount"} — always namespaced, never bare {"var": "amount"})
  • The list suffixes compile away: .Value becomes index .0 ({"var": "document.kw.Status.0"}) and .Count becomes the count operator
  • Object literals compile to the recordOf operator
The formula and JSON forms round-trip: the designer renders stored JSONLogic back as formula text wherever a textual form exists.

Rules that bite

  • Secrets never appear in guards or filters. Referencing $secrets.* in a condition is a publish-blocking error; this prevents secret values leaking through observable branching.
  • References are validated advisorily. Publish checks keyword, group, and attribute names against the workflow’s scope and warns on anything it can’t resolve — fix the typo or the scope.

Tasks & the end-user experience

What workflows look like for the people who complete tasks — the Tasks page, task forms, and workflow badges in search results.

Action reference

The parameters these expressions feed into.