Workflow nodes pass data to each other over wires, and {{ }} templates let you use that data inside any text setting — a prompt, a URL, an email subject, a request body. Templates reference wired inputs and the outputs of earlier nodes, and can embed a small expression language for reshaping values inline.

Template basics

Text settings in a node’s configuration accept {{ }} references:

  • {{name}} — a wired input, by its handle name
  • {{analysis.summary}} — a nested field of a wired input
  • {{nodes.fetch-user.response.body.email}} — any earlier node’s output, addressed as nodes.<node-id>.<output>.<path>

Spaces inside the braces are fine: {{ name }} and {{name}} resolve identically.

https://api.example.com/users/{{nodes.fetch-user.response.body.id}}

Two kinds of settings never treat {{ }} as a template: code editors (the Code node’s source is delivered verbatim) and the condition fields on control-flow nodes, which take expressions directly — see “Condition fields” below.

How values render

Templates produce text. A referenced value is inserted as:

  • Strings — as-is
  • Numbers and booleans — as text (42, true)
  • Lists of strings — comma-separated (a, b, c)
  • Other lists and objects — JSON
  • Null — an empty string

Inserting variables

You rarely need to type addresses by hand:

  • On the canvas, node fields show an Insert variable button. It opens a grouped picker of the trigger and every upstream node’s outputs and inserts the address at the cursor.
  • In the settings sidebar, typing {{ in a text field opens an autocomplete menu. Entries are labeled with the node’s name and output path, narrow as you type, and insert the full address. Condition fields have the same autocomplete for input paths and functions.
  • After the workflow has run at least once, picker entries show sample values from previous runs, and a preview line under the field shows what the setting resolves to.

Expressions

When the content between {{ }} is more than a plain dotted path, it is evaluated as an expression:

{{ upper(name) }}
{{ price * qty }}
{{ default(nodes.fetch-user.response.body.tag, "n/a") }}
{{ items[0].id }}

Operators and literals

  • Logic: &&, ||, !
  • Comparison: ==, !=, >, >=, <, <=
  • Arithmetic: +, -, *, /, % (remainder)
  • Access: .field, [index]
  • Literals: numbers, double-quoted strings ("done"), true, false, null, arrays ([1, 2, 3]), (…) grouping

Semantics to know:

  • Only null and false are falsey — "", 0, and [] are truthy. Test for non-empty with len(x) > 0.
  • A missing field or out-of-range index reads as null, never an error.
  • Arithmetic coerces numeric strings; a null operand or division by zero yields null.
  • == compares by value: 1 == 1.0 is true.
  • Ordering comparisons (>, <, …) work between two numbers or two strings; anything else is false.
  • Strings use double quotes only, with \n, \t, \", and \\ escapes.

Functions

Group Functions
Logic if(cond, a, b) — only the taken branch is evaluated; default(value, fallback) and coalesce(a, b, …) — first argument that is not null or an empty string
String concat(…), lower(s), upper(s), trim(s), replace(s, find, replacement), regex_replace(s, pattern, replacement), regex_match(s, pattern), mime_match(type, glob) (e.g. mime_match(input.content_type, "image/*")), strip_html(s), split(s, sep), join(list, sep)
Numbers round(n, precision), min(a, b), max(a, b), clamp(n, lo, hi), to_number(s)
Collections len(x) — string, list, or object size; contains(x, v) — substring or list membership; slice(x, start, length) — substring or sublist, length optional; get(map, key) — read a field, dotted paths allowed
Convert to_string(v), json_parse(s), to_json(v), xml_parse(s)
Time now_iso(), now_unix(), now_unix_ms(), to_unix(iso), to_unix_ms(iso), from_unix(n), from_unix_ms(n), format_date(ts, format) or format_date(ts, format, timezone) — strftime format, optional IANA time zone

The now_* functions read a clock that is stamped once per Run, so every node in the same Run sees the same timestamp.

Condition fields

The If, Switch, and Loop nodes route on expressions, and their condition fields take the language directly — no braces. The scope is the node’s wired inputs at the root:

input.status == "done" && len(input.items) > 0
  • If routes its input to the true or false output on one condition.
  • Switch evaluates a list of cases (condition + output name); the first match wins. In partition mode it splits a list across the cases with a per-item condition that reads item and index.
  • Loop accepts an exit condition that reads state and iteration.
  • Map / Transform operations use the same language per list item, with item and index in scope.

Condition fields are fail-closed: a blank or invalid condition fails the node before anything routes, and an evaluation error fails the node. The editor highlights the expression as you type, flags compile errors inline, and shows a live result preview once sample data from a previous run is available.

When a reference does not resolve

  • A {{path}} reference that resolves to null renders as an empty string.
  • A {{path}} reference that cannot resolve — the node does not exist, the output name is wrong, or the upstream node was skipped and produced nothing — fails the node before it executes, with an error naming the variable. The literal {{…}} text never reaches an API, prompt, or shell command.
  • Inside an expression, a missing path reads as null — wrap it in default(…) to substitute. An unknown function or an evaluation error fails the node.
  • At save time the editor validates every {{nodes.…}} address against the graph, so an address naming a missing node or output fails the save rather than the run. References to wired input handles are checked at run time.

Literal braces and other template engines

Authored text often carries another engine’s template syntax — shell scripts writing GitHub Actions or Helm files, for example. These pass through untouched:

  • ${{ … }} (GitHub Actions) is never treated as a Rollout template.
  • Leading-dot references like {{ .Values.image }} (Helm/Go) pass through — Rollout references start with a letter, digit, or underscore.
  • Any span that does not parse as a path or expression — {{ var | filter }} — passes through verbatim.
  • To force literal braces, escape them: \{\{ name \}\} renders as {{ name }} without being resolved.

Limits

  • An expression can be at most 10,000 bytes, with bounded nesting and complexity.
  • Regex patterns are capped at 1,000 bytes and regex inputs at 100,000 bytes.
  • A {{ }} span cannot cross a line break.

See Visual Workflows for the editor basics, Node Reference for the full node catalog, and API Request for templating a request end to end.