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.$ 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:Ordinary document and Caseflow events start with an empty variable bag, so
$instance.<name> only resolves after something has set it. Mail imported is an exception: it seeds mail metadata and archived document IDs at instance creation. So does a workflow document action: it seeds the variables stored in its configuration and, when it asks for confirmation, the chosen value as $instance.choice — or under the variable name the action sets. An unset variable resolves to null — check that the chosen start path supplies it before using it in a guard.The owner of a run
$owner.username in a formula, or {{owner.username}} in a template, is the username of the person whose action started the run. It is recorded when the run starts and does not change, so a step after a Join, a timer, or a task someone else completed still reads the person who started it. Use it to write that person’s name into a note, pass it to a script, or compare it with $identity.username.
username is the only member: publishing rejects any other, such as $owner.email. Always read $owner.username, not $owner on its own: that is a record, not text, so it never equals a username, and publishing does not catch it. A trigger filter cannot read $owner, because no run exists yet. The same owner decides who sees a task visible to the person who started the workflow, and is shown as Owner under Workflow Instances.
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). Caseflow 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. It uses the samekw and group paths as the rest of the workflow, plus a few fields only the event has:
- Document events expose
$document.id,$document.documentTypeId,$document.documentTypeName,$document.kw.<Name>, and$document.group.<Name> document.deletedalso exposes$documentTypeIdat the top level: the deleted document’s type as the event carries it, since the$documentbranch is null on a delete.document.createdlists it too, but it is empty for a document a mailbox import stored, so filter a create on$document.documentTypeId- Caseflow events expose
$object.id,$object.classId,$object.className, and$object.attr.<Name>. When the event deleted the object, the whole$objectbranch is null —$object.idand$object.attrincluded — and only the event’s own top-level fields remain, such as$objectIdand$className - The event’s own fields sit at the top level — for example
$username, the user whose action raised the event - Mail imported also exposes
mail,mailbox,mailboxConfigurationId,archiveRuleName, anddocuments; see the mail trigger example - A
document.createdevent can carry an answer to an interaction rule, exposed as$interaction.value(the chosen choice’s value),$interaction.label,$interaction.ruleId, and$interaction.variables.<name> $instance.*,$item.*,$identity.*,$owner.*,$env.*, and$secrets.*are not available, and publishing rejects a filter that reads them
$document.* or $object.* paths, event fields such as $document.documentTypeId and $username, the $mail.* fields on Mail imported, and $interaction.value, $interaction.label, $interaction.ruleId, and $interaction.variables on document.created. It cannot know the variable names an interaction rule maps, so it marks $interaction.variables.<name> as an unknown reference. That is a warning, not an error: the filter saves, publishes, and matches, so check the name against the rule’s outcome yourself.
On Caseflow object deleted, the object is gone by the time the filter runs, so the whole $object branch is null. The editor offers none of its references there and warns about any the filter uses: a comparison with one never matches, and a negated one matches every event. Filter that trigger on the event’s own fields instead, such as $className or $username.
On document.deleted the $document branch is null in the same way, so the editor offers none of its references there either. The delete event carries the document’s type, and the scope gate checks it against the workflow’s document types before the filter runs. To narrow a delete further, filter on the event’s own fields, such as $documentTypeId == 150 for one type of a workflow scoped to several, or $username. An older Insight version that does not send the document type on delete events makes the scope gate look the document up instead, and a deleted document is no longer found, so its deletes start nothing.
Filtering on an interaction answer
$interaction is null unless an answered rule names the definition being matched, so $interaction.value == "yes" starts the workflow on that answer alone.
Always test for the answer you want. $interaction.value != "no" is also true for every document nobody was asked about — one stored by an integration, a form, or a script, or one no rule matched.
$interaction is the answer of the first answered rule, in evaluation order, that names the definition, and $interaction.variables.<name> holds the variables that one rule maps, as text. The instance that starts receives the mapped variables of every answered rule that names the definition, as $instance.<name>; where two rules map the same name, the earlier rule wins.
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.
{{= … =}}, 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:
.Valuebecomes index.0({"var": "document.kw.Status.0"}) and.Countbecomes thecountoperator - Object literals compile to the
recordOfoperator
Follow an input into a decision
The contract-review walkthrough defines a required task input namedreviewNote. After completion, it is available as $instance.reviewNote for subsequent actions. Before task completion that value may not exist, so do not use it as a trigger filter or assume it was present when the instance first entered the review node.
If a Script.Run action returns a decision, capture its output into the configured instance variable and inspect that variable in a test instance before adding a guard that reads it. This separates an incorrect reference path from an incorrect calculation.
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.
Where to read next
Task access and validation
Verify workflow task visibility, decision forms, completion behavior, and badges with representative accounts.
Action reference
The parameters these expressions feed into.
