Templates & expressions

Templates wire your blocks together. A {{ }} expression inside any config field is resolved before the block runs, against the data produced by earlier steps. The same grammar powers both the in-browser simulator and the compiled Go binary, so what you test is what you ship.

Syntax

Write {{ path }} anywhere in a string. Two forms behave differently:

  • Interpolation — the expression is part of a larger string. It is replaced by its value as text; null/undefined become empty.
    "Hello, {{ steps.name.value }}!"   ->   "Hello, Ada!"
    "{{ steps.fetch.body.login }} is active"
  • Whole value — the entire field is a single {{ }}. The raw, typed value is returned (object, array, number, boolean), not a string. Use this for fields that expect structured data.
    "{{ steps.fetch.body }}"   ->   { login: "ada", followers: 1234 }
    "{{ steps.choice.value }}"  ->   true   (boolean, not "true")

Templates recurse into objects and arrays, so a reference inside headers or query is resolved too.

The context

Paths resolve against the execution context, which has two roots:

stepsEach step’s output, keyed by id. Fields depend on the block — see the Block reference.
envEnvironment variables of the running process. Use this for secrets.
"{{ steps.username.value }}"        # a previous text_input
"{{ steps.fetch.body.email }}"     # a nested JSON field
"{{ env.API_KEY }}"                # an environment variable

Paths and indexing

Navigate with dots. Index into arrays with [i].

"{{ steps.fetch.body }}"             # whole object
"{{ steps.fetch.body.login }}"       # nested key
"{{ steps.items.body[0] }}"          # first element of an array
"{{ steps.tags.values[2] }}"         # third multi_select choice

A missing key resolves to undefined (empty in interpolation; the field is left blank, not an error).

Condition expressions

The condition block evaluates an expression after resolving its templates. The grammar is deliberately tiny:

OperatorMeaning
== !=Strict equality (same type AND value). No coercion.
> < >= <=Numeric comparison. Non-numbers → false.
andAll parts must be true.
orAny part must be true.

Literals are recognized automatically: true/false, numbers, and quoted-less bare strings.

"{{ steps.fetch.status }} == 200"
"{{ steps.confirm.value }} != false"
"{{ steps.fetch.body.followers }} > 1000"
"{{ steps.env.value }} == prod and {{ steps.dryrun.value }} == false"

A comparison with no operator is tested for truthiness. The falsy values are "", false, 0, null, and undefined; everything else is truthy.

Limitations (be aware)

The expression engine is intentionally minimal. It does not support:

  • Nested or mixed and/or with precedence — keep conditions to a single and or a single or.
  • A not operator — invert with == false or !=.
  • Arithmetic, string concatenation, or parentheses.
  • Regular-expression matching. Use a set_variable plus a comparison instead.

For anything richer, the downloaded project is plain Go — edit internal/blocks.go and rebuild.

Worked examples

Chain a template through set_variable — compute a greeting, then print it:

[
  { "id": "name", "type": "text_input", "config": { "label": "Name" } },
  { "id": "g", "type": "set_variable",
    "config": { "value": "Hello, {{ steps.name.value }}!" } },
  { "id": "out", "type": "output_text",
    "config": { "text": "{{ steps.g.value }}", "style": "success" } }
]

Branch on an HTTP status — fetch, then show a table or an error:

[
  { "id": "u", "type": "text_input", "config": { "label": "GitHub user" } },
  { "id": "fetch", "type": "http_request", "config": {
    "method": "GET", "url": "https://api.github.com/users/{{ steps.u.value }}" } },
  { "id": "ok", "type": "condition",
    "config": { "expression": "{{ steps.fetch.status }} == 200" },
    "branches": { "true": "show", "false": "err" } },
  { "id": "show", "type": "output_table", "config": {
    "title": "User", "columns": ["login","followers"], "data": "{{ steps.fetch.body }}" } },
  { "id": "err", "type": "output_text",
    "config": { "text": "Not found", "style": "error" } }
]

POST with a JSON body — send the output of an earlier step:

{ "id": "post", "type": "http_request", "config": {
  "method": "POST",
  "url": "https://example.com/api",
  "headers": { "Content-Type": "application/json", "Authorization": "Bearer {{ env.TOKEN }}" },
  "body": "{{ steps.form.value }}" } }