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 asnodes.<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
nullandfalseare falsey —"",0, and[]are truthy. Test for non-empty withlen(x) > 0. -
A missing field or out-of-range index reads as
null, never an error. -
Arithmetic coerces numeric strings; a
nulloperand or division by zero yieldsnull. -
==compares by value:1 == 1.0is true. -
Ordering comparisons (
>,<, …) work between two numbers or two strings; anything else isfalse. -
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
trueorfalseoutput 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
itemandindex. -
Loop accepts an exit condition that reads
stateanditeration. -
Map / Transform operations use the same language per list item, with
itemandindexin 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 tonullrenders 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 indefault(…)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.