Expressions

Any text field on a node can contain an expression. Expressions are how a node reads what the nodes before it produced. They are evaluated once per node, immediately before that node runs.

Syntax

Wrap a path in double braces. Everything outside the braces is used literally, so an expression can be embedded in a larger string.

Hello {{$json.customer.name}}, your order {{$json.orderId}} shipped.

A node name containing spaces goes in square brackets, unquoted:

{{$node.[HTTP Request].httpResponse.data.title}}

What you can reference

  • {{$json.field}}The accumulated output of every node that has run so far in this execution. This is the one you want most of the time.
  • {{$node.[Node name].field}}The output of one specific upstream node, by its name on the canvas. Use this when two nodes produce the same key and you need a particular one. Note the square brackets and the absence of quotes — they are what allow a name containing spaces; adding quotes looks for a key that literally contains them.
  • {{$execution.id}}The id of the current run. Useful as an idempotency key in a downstream system.
  • {{$workflow.id}}The id of the workflow being run.
  • {{$now}}ISO-8601 timestamp, fixed for the whole node so two fields on the same node cannot disagree.
  • {{field}}Top-level keys of the accumulated output are also readable without the $json prefix.

Escaping — read this one

Values interpolated with {{ }} are HTML-escaped. That is right for text, and wrong for almost everything else — a JSON body built with {{ }} becomes invalid the first time a value contains a quote or an ampersand.

{{name}}     →  O'Brien & Sons     (escaped — breaks JSON)
{{{name}}}   →  O'Brien & Sons          (raw — use this in a body)
{{json obj}} →  { "a": 1, "b": [2, 3] } (serialises a whole object)

Rules of thumb: triple braces when writing into JSON, SQL, or a URL. The json helper when you want an entire object rather than one scalar — interpolating an object with double braces renders [object Object], which is rarely what anyone wanted.

Missing values do not fail

A path that does not resolve renders as an empty string. It is not an error, and the run continues. This is deliberate — a partially populated payload is usually better than a failed run — but it does mean a typo in a path is silent. If a downstream system receives an empty field, suspect the expression before suspecting the data.

{{$json.custmoer.name}}   →  ""   (typo renders empty, run continues)

Porting from n8n

AutoFlow is largely n8n-compatible. Most templates migrate by rewriting a small set of idioms. The full written map lives in docs/nodes/expressions.md; the common cases:

  • n8n: {{$json.body.x}} → {{webhook.body.x}}The webhook payload is under the webhook root. A bare {{body.x}} or {{$json.body.x}} is flagged at save time.
  • n8n: {{$json.x}} → {{x}}The accumulated context is flat; reference top-level keys directly.
  • n8n: {{$node["N"].json.x}} → {{$node.N.x}}A node's output by its canvas name; use square brackets, unquoted, for names with spaces.
  • n8n: {{ a || b }} → {{default a b}}Loose fallback — a when present and non-empty, else b.
  • n8n: {{ a?.b }} → {{get a "b"}}Safe path access; a missing path renders empty rather than failing.
  • n8n: {{ n/100 }} → {{div n 100}}Arithmetic is helper-based: add, sub, mul, div. Also gt/gte/lt/lte, and/or/not, len, upper, lower.
  • n8n: {{ $now }} → {{formatDate $now "yyyy-MM-dd HH:mm:ss"}}Time formatting is explicit, with a date-fns pattern.

A template that references a root the workflow cannot produce (for example a webhook.* root on a workflow that has no Webhook trigger, or a stale $json.body) is a warning at save time, not a silent empty render — so a ported expression fails loudly instead of resolving to "" at run time.

What expressions deliberately cannot do

Expressions are not JavaScript. There is no eval, no new Function, and no VM anywhere in the evaluation path. The limitation is the security control, not an unfinished feature: this system holds every customer's credentials, and arbitrary code in a template field would be remote code execution against that store.

  • No arbitrary code, arithmetic, or method calls inside an expression.
  • No access to constructor, __proto__, or any inherited property — only own properties of the run's data.
  • No access to environment variables, globals, or the file system.
  • Transformation belongs in a node (Set, Condition, or an AI node), where it is visible on the canvas and recorded in the trace.

Seeing what a node actually received

Every node records its resolved input and its output on the execution trace, so the fastest way to fix an expression is to open the run and read what the node was actually given, rather than reasoning about what it should have been. Note that inputs and outputs are redacted once a run passes your plan's retention window — debug recent runs.