GoRunner
Core concepts

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

ExpressionResolves 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.

On this page