Skip to content

Workflow expressions ​

Node settings that depend on data use expressions, written in a small subset of the Jinja expression syntax. There is one expression language for all nodes: the same rules apply to conditions, messages, mappings, URLs and query variables.

Two kinds of fields ​

Predicate fields hold a bare expression, with no braces. These are condition's expression, each switch case's expression, assert's condition and logic's operands:

yaml
expression: nodes.fetch_job.output.data.getJob.quantity > 1000

Value and text fields are everything else that accepts expressions, such as messages, mappings, variables, URLs and bodies. There you wrap the expression in double curly braces:

yaml
# A single expression and nothing else: the typed value (number, list, object...)
itemCount: "{{ input.items | length }}"

# Text with expressions in it: always a string
message: "Order {{ input.orderId }} has {{ input.items | length }} items"

# No braces: the literal text
label: static text

A field that contains exactly one braced expression and nothing else keeps the value's type. Any surrounding text, or more than one expression, turns the result into a string. Dates are rendered as ISO 8601 text.

In YAML, quote any value that starts with a brace, as in the examples.

References ​

ReferenceValue
input.<field>A field of the workflow input
nodes.<id>.output.<field>A field of an earlier node's output
variables.<name>A workflow variable (declared, or set by set_variable)
  • .output is always written out: nodes.fetch.output.status, not nodes.fetch.status.
  • nodes.<x> finds the node by id first, then by name. Prefer ids, because names can contain spaces.
  • Use brackets for indexes and unusual keys: input.items[0], input["order-id"].
  • A path that does not exist gives null instead of an error. Use default to supply a fallback.
  • Inside a for_each body, the current item is available under the loop's as name (item by default), and its position as index.

What a node's output contains depends on the node type. See the node reference.

Operators ​

KindOperators
Comparison== != < <= > >=
Logicand or not
Membershipin not in
Arithmetic+ - * / // (whole-number division) %
Text concatenation~
Conditionala if condition else b
Grouping( … )

Literals: numbers, strings in single or double quotes, true, false, null (or none), lists [1, 2, 3] and objects written as JSON-style key/value pairs.

Comparing 1 == 1.0 is true: numbers compare by value. Dates compare in time order. The ordered comparisons (<, <=, >, >=) need two numbers, two strings or two dates. Comparing null with a number fails the node, so give values that may be missing a fallback: (nodes.fetch.output.data.getJob.quantity | default(0)) > 1000.

There are no statements or blocks ({% %}) and no comments. For branching and loops, use the condition, switch and for_each nodes.

Filters ​

Filters transform a value: value | filter or value | filter(argument). They can be chained: input.name | trim | upper. Only these filters exist:

FilterResult
default(fallback)The value, or fallback if the value is null
lengthLength of a string, list or object
min, maxSmallest or largest item of a non-empty list
lower, upper, trimChanged text
join, join(separator)List items joined into a string (no separator by default)
absAbsolute value of a number
round, round(digits)Rounded number
merge(other)Object with the keys of other added; keys in other win
pick('a', 'b', …)Object with only the named keys
omit('a', 'b', …)Object without the named keys
concat(other)Two lists (or strings) joined

Filters bind more tightly than every operator, so input.items | length > 10 compares the length. Parentheses, as in (input.items | length) > 10, make that easier to read.

There are no regular expressions in expressions; use the regex node for that.

Truthiness ​

Predicates and and / or / not treat these values as false: null, false, 0, 0.0, "", [] and an empty object. Everything else is true, including every date.

Examples ​

GoalExpression
Quantity over a thresholdnodes.fetch.output.data.getJob.quantity > 1000
Two conditionsinput.amount > 1000 and input.status == "approved"
Value from a listinput.priority in ["high", "urgent"]
Fallbackinput.customer | default("unknown")
Count itemsinput.items | length
Build a label"Job " ~ input.jobNumber ~ " (" ~ input.status ~ ")"
Pick a tier"large" if input.total >= 1000 else "standard"

This workflow uses most of these features. It passes validation as shown:

yaml
nodes:
  - id: defaults
    name: Set defaults
    type: set_variable
    config:
      variables:
        threshold: "{{ input.threshold | default(1000) }}"
        label: "{{ input.customer | default('unknown') | upper }}"
  - id: fan_out
    name: Fan out
    type: split
  - id: summary
    name: Build summary
    type: transform
    config:
      mapping:
        title: "Order {{ input.orderId }} for {{ variables.label }}"
        itemCount: "{{ input.items | length }}"
        isBig: "{{ (input.items | length) > 10 or input.total >= variables.threshold }}"
        tier: "{{ 'large' if input.total >= variables.threshold else 'standard' }}"
        firstItem: "{{ input.items[0] | pick('sku', 'qty') }}"
  - id: each_item
    name: Log each item
    type: for_each
    config:
      items: "{{ input.items }}"
      as: item
      maxConcurrency: 4
      body:
        type: log
        config:
          message: "Item {{ index }}: {{ item.sku ~ ' x ' ~ item.qty }}"
  - id: both
    name: Wait for both
    type: join
    config:
      strategy: keyed
      keys:
        summary: summary
        each_item: items
      onPartialFailure: fail
  - id: done
    name: Done
    type: log
    config:
      message: "{{ nodes.summary.output.title }}: {{ nodes.each_item.output.count }} items logged"
edges:
  - id: e1
    fromNodeId: defaults
    toNodeId: fan_out
  - id: e2
    fromNodeId: fan_out
    toNodeId: summary
  - id: e3
    fromNodeId: fan_out
    toNodeId: each_item
  - id: e4
    fromNodeId: summary
    toNodeId: both
  - id: e5
    fromNodeId: each_item
    toNodeId: both
  - id: e6
    fromNodeId: both
    toNodeId: done

Connect machines, automate workflows, and build custom apps for print production.