Skip to content

Templates and variables

Every text field on a node is a template: an Action’s input, an Agent’s Task, a Notification’s fields, a Database node’s SQL, and the values a Conditional or Decision compares. Templates are written in Liquid, the template language Shopify created, as the LiquidJS library renders it. Text outside {{ }} and {% %} stays as you wrote it.

Type {{ in a field to pick a variable. The list offers the step before, the trigger payload and every other node’s output fields. With a run open in the builder, it also offers the fields that run recorded.

Variable What it reads
{{ input }} The text of the step that fired into this one
{{ input.<field> }} One output field of that step
{{ trigger }} The payload of the trigger that started the run
{{ trigger.<field> }} One field of a JSON payload, such as {{ trigger.customer.email }}
{{ nodes.<id>.<field> }} One output field of any step that ran earlier, by its node’s id

Read nested values with dots and list items by position, from 0: {{ nodes.lookup.rows[0].name }}, or {{ nodes.lookup.rows.first.name }}.

A node’s id appears on the run page, where each output field is labelled nodes.<id>.<field>. The variable picker writes it for you. Each node’s output fields are listed on its page under Nodes.

{{ input }} is the text the step before produced:

Step Its text
Trigger The payload as it arrived
Action What the action returned, as JSON
Agent The agent’s reply
Database The rows, as JSON
Notification The email’s message; a webhook passes on what it received
Conditional, Decision, Delay The text they received, unchanged

After a Conditional, Decision or Delay, {{ input.<field> }} reads that node’s own output. Read the fields of the step before it as {{ nodes.<id>.<field> }}.

  • Text and numbers print as they are.
  • An object or a list prints as its JSON.
  • A missing variable prints nothing, so a misspelled name shows up as a blank, not an error.

A filter changes a value, {{ value | filter: argument }}, and filters chain left to right:

{{ trigger.name | default: "there" }}
{{ trigger.subject | upcase | truncate: 60 }}
{{ nodes.lookup.rows | map: "name" | join: ", " }}
{{ nodes.lookup.rows | where: "plan", "pro" | size }}
{{ trigger.amount | times: 1.2 | round: 2 }}
{{ "now" | date: "%Y-%m-%d %H:%M", "Europe/Berlin" }}

Every filter LiquidJS provides is there: text filters such as strip, replace, split and append, list filters such as first, last, sort, uniq and where, math such as plus and divided_by, date, and json. A filter name that doesn’t exist leaves the value unchanged rather than failing, so check the output when a filter seems to do nothing.

{% if trigger.amount >= 500 %}Large order{% elsif trigger.amount > 0 %}Order{% else %}Empty{% endif %}
{% for row in nodes.lookup.rows %}
{{ forloop.index }}. {{ row.name }}
{% else %}
No rows.
{% endfor %}
  • Compare with ==, !=, >, <, >=, <= and contains.
  • Combine with and and or. There are no parentheses, so nest {% if %} tags instead.
  • {% unless %}, {% case %}, {% assign %} and {% capture %} work as in any Liquid.
  • {%- and -%} trim the whitespace beside a tag, so a loop leaves no blank lines.

An Action’s input and a webhook body are read as JSON after they render. A value with quotes or line breaks, such as an agent’s reply, breaks a quoted string. Use the json filter with no quotes around it. It writes the value as a JSON string, number, object or list:

{"text": {{ nodes.reply.response | json }}, "rows": {{ nodes.lookup.rows | json }}}

In a Database node, each {{ }} becomes a bound parameter, never part of the statement, and {% %} tags are refused. See Database.

A template that isn’t valid Liquid, such as an {% if %} without its {% endif %}, is caught when you publish. The error names the node, the line and the column. Run draft doesn’t check templates first: the step fails with the same message.