Expressions & pipes
Anywhere a node takes a template — an email body, a SQL statement, a Sheets cell, an HTTP header — you can drop {{payload.field}} and it is replaced with the value from the payload the node received. Pipes transform that value on the way out.
Referencing a value
A reference is a path into the incoming payload. Dots walk into nested objects, and a key with spaces in it works as-is — no quoting needed.
{{payload.email}}
{{payload.customer.plan}}
{{payload.row.Talla de camiseta}}To read the output of a different node instead of the one right before you, name it: {{$("Fetch orders").rows}}. It finds the nodes of this run in this workspace. A flow reached through a flow link from another workspace sees its own nodes, not the caller's — only the payload crosses.
Transforming with pipes
Chain transformations with |. Arguments go in parentheses, and each pipe receives the result of the one before it.
{{payload.size | substring(0,4)}} → S-20
{{payload.size | replaceAll('-','')}} → S2026XL
{{payload.size | substring(0,4) | replaceAll('-','')}} → S20
{{payload.name | trim | upper}} → ANA
{{payload.items | unique | sort | join('-')}} → a-b-c
{{payload.items | join(', ')}} → a, b, c{{payload.size.substring(0,4)}} looks like JavaScript, but it is read as a path — there is no field called substring(0,4), so the expression resolves to an empty value. Use the pipe form above. Colon-separated arguments (| substring: 0, 4) are not the syntax either.What you can pipe through
Text — upper, lower, trim, replace, replaceAll, substring, charAt, padStart, padEnd, startsWith, endsWith, includes, length.
Lists — join, first, last, at, count, reverse, sort, unique, flatten.
Dates — formatDate, formatTime, formatDateTime, toISO, toEpoch, diffDays, diffHours, addDays, addHours.
Numbers and shape — round, currency, percent, add, subtract, multiply, divide, number, string, boolean, json, toJSONString, preview.
json and toJSONString both turn a value into JSON text. json indents it, for reading in a message; toJSONString keeps it on one line, for putting an object inside another JSON body or a query parameter. A string comes out in quotes, because that is its JSON; a field that isn't there comes out empty.
{{payload.order | toJSONString}} → {"id":7,"items":["a","b"]}
{{payload.order | json}} → {
"id": 7,
"items": ["a", "b"]
}
{{payload.name | toJSONString}} → "Ana"{{payload.ratio | percent}} → 73% (payload.ratio is 0.732)
{{payload.ratio | percent(2)}} → 73.20%
{{payload.total | round(2)}} → 1500.00
{{payload.total | currency}} → $1,500.00percent takes a fraction and scales it by 100, the same way Angular's PercentPipe does: 0.732 becomes 73%. There are no decimals by default and the result is rounded rather than truncated, so 0.7351 becomes 74%. Ask for decimals with an argument, exactly as you would with round: percent(2) gives 73.20%.percent used to append the symbol without scaling, so a field already stored as a whole percentage — 73.2 — rendered as 73.2%. It now renders as 7320%, with no error and no warning, in every email, cell and message that template feeds. If your value is already a percentage, either divide it by 100 at the source or drop the pipe and write the symbol yourself: {{payload.rate | round(1)}}%.Doing arithmetic
add, subtract, multiply and divide take one argument — a number, or a path to another field — and return the result. Writing the operator outside the braces does not work: {{payload.in_stock}} - 1 is text, so it renders as 10 - 1, unevaluated.
{{payload.in_stock | subtract(1)}} → 9
{{payload.in_stock | subtract(payload.ordered)}} → 7
{{payload.price | multiply(payload.qty)}} → 59.97
{{payload.subtotal | add(payload.shipping) | currency}} → $262.50
{{payload.ordered | divide(payload.in_stock) | percent}} → 30%
{{payload.in_stock | divide(3) | round(2)}} → 3.33
(in_stock is 10, ordered 3, price 19.99, qty 3, subtotal 250, shipping 12.5)The field-reference form is the one you usually want: subtract(payload.ordered) is “stock minus what was ordered”, which is a different job from subtracting a constant. A literal is tried first, so subtract(1) never goes looking for a field called 1.
0.1 + 0.2 is 0.30000000000000004 and 19.99 - 0.99 is 19.000000000000004. Through these pipes they come out as 0.3 and 19. What is not trimmed is real precision: {{payload.in_stock | divide(3)}} gives 3.33333333333333, so add | round(2) when you are printing the number for a person.{{payload.in_stock | divide(0)}} renders as nothing at all. That is deliberate: the word Infinity printed into a spreadsheet cell breaks every SUM down that column, and in an email it reads as a broken system. An argument that points at a field which does not exist behaves the same way — {{payload.in_stock | subtract(payload.typo)}} is empty rather than an untouched 10, so a calculation that did not happen never looks like one that did. A value that is not a number is empty too, exactly like round and currency.In HTML emails: raw
In the HTML body of an Email, Gmail, Approval or Outlook node, every expression is escaped: its value comes out as text, never as HTML. To insert HTML you trust, end the expression with | raw.
<p>Hi {{payload.name}},</p> → the name, as text
<div>{{$("AI").aiResponse | raw}}</div> → the AI Node's HTML, as HTMLraw only for HTML your own flow produced. A value that came from a webhook, a form or an email can carry tags and links that end up in what you send.When an expression does not resolve
A reference that points at nothing — a misspelled field, a path that stops halfway, a pipe that does not exist — resolves to an empty value. The node still runs. That is deliberate: a missing optional field should not take a whole delivery down.
The cost is that an empty value can look like a working one. A SQL template with an unresolved reference binds '' and the query returns no rows; an email subject renders with a gap. So the engine reports it instead of staying quiet:
Template {{payload.talla | noExiste(1)}} came out empty — unknown filter "noExiste"That line lands in Monitoring → Logs, tied to the run it came from, and it also shows while you are testing a single node with Run. Warnings are deduplicated per run and capped, so a loop over 500 rows does not write 500 identical lines — when the cap is reached, a final line says so rather than silently dropping the rest.
template_issue. The message names the expression and what went wrong with it.