Expressions
Wiring data between steps with {{ … }} — paths for the common case, JavaScript when you need it.
Any config value can interpolate data from earlier in the run with double-brace expressions.
The namespaces
| Expression | Resolves to |
|---|---|
{{trigger.body.email}} | The trigger's payload (webhook body, form fields, tool arguments). |
{{steps.fetch.body.name}} | A previous step's output, addressed by its stable name. |
{{steps.parse.result}} | A Code step's return value lives under .result. |
{{loop.item}} / {{loop.index}} | The current element inside a loop body. |
{{$vars.API_URL}} | A workflow variable — plain or sensitive. |
{{steps.wait.resumeData}} | The body posted to a resume webhook while the run was paused. |
Plain dot/index paths (a.b[0].c) resolve deterministically. Anything more — method calls, arithmetic, ternaries — is evaluated as JavaScript in the sandbox: {{steps.fetch.body.items.length > 3 ? "big" : "small"}}.
Types are preserved
An expression that is the entire value keeps its native type: {{steps.fetch.body}} passes the object itself, not a string rendering of it. Mixed into text, values are stringified: "Order {{trigger.body.id}} received".
Objects inside JSON strings
When building a JSON body by hand, an object-valued expression must be the whole unquoted value — {"payload": {{trigger.body}}} — never embedded inside a quoted string, where the first quote in the data would shatter the JSON. Restructure, or stringify in a Code step.
Mapping without guessing
You never write expressions blind:
- Every trigger and most actions declare a sample payload, so the data picker offers real field names before anything has run.
- After a test run, the picker offers the run's actual outputs, and you can pin a step's output so later test runs replay it (pins apply to test runs only — production always executes for real).
- Keel's flight simulator dry-runs proposed expressions against declared samples and flags a wire that would carry nothing before you apply the proposal.