Variables and Liquid
Everything dynamic in this app is Liquid - the same templating language Shopify themes use. You write a placeholder, and the app fills it in at send time.
The four namespaces
| Namespace | Where the value comes from | Available in |
|---|---|---|
{{ variables.key }} |
The Variables (JSON) field on the Shopify Flow action | Email subject and body, HTTP URL, headers and body |
{{ secrets.key }} |
The Secrets page | Same, plus SMTP username and password |
{{ shop.name }}, {{ shop.domain }}, {{ shop.email }} |
Your store | Email and HTTP actions |
{{ flow.key }} |
The raw Shopify Flow payload | HTTP requests only |
The System Variables card in the editor lists the shop values with their current contents, and clicking one copies it.
Passing variables from Shopify Flow
In the Flow action, put a JSON object in the Variables (JSON) field:
{
"firstName": "{{ customer.firstName }}",
"orderNumber": "{{ order.name }}",
"total": "{{ order.totalPriceSet.shopMoney.amount }}"
}
Then reference them anywhere in the layout:
Hi {{ variables.firstName }}, your order {{ variables.orderNumber }} is on its way.
The keys are yours to choose. Whatever you name on the left is what you use after variables., matching exactly on spelling and capitalisation.
You do not have to write this JSON by hand. Every layout has a Use in Shopify Flow card with the correct JSON ready to copy - see Passing data from Shopify Flow.
Filters
Standard Liquid filters work. The most useful one is default, which protects you from an empty value:
Hi {{ variables.firstName | default: "there" }}
Others behave as you would expect:
{{ variables.productTitle | upcase }}
{{ variables.total | round: 2 }}
{{ variables.note | truncate: 100 }}
When a variable renders blank
A live send that renders any referenced variable to nothing gets an Empty variables badge in History. Opening the entry names them and separates two different problems:
- Sent by Shopify Flow but empty - the key was in your JSON, but Flow produced no value. Usually a wrong property path, or a field that is genuinely empty for that record (a guest checkout has no customer first name).
- Not sent by Shopify Flow at all - the key is missing from your JSON entirely.
Adding a | default: fallback marks the blank as intentional, and the app stops warning about it.
Conditionals and loops
Use the HTML / Code block for conditionals:
{% if variables.isVip %}
<p>As a VIP you get free shipping on this order.</p>
{% else %}
<p>Spend a little more to unlock free shipping.</p>
{% endif %}
Use the Loop block to repeat over an array, for example order line items:
{% for item in variables.items %}
<tr>
<td>{{ item.title }}</td>
<td>{{ item.quantity }}</td>
<td>{{ item.price }}</td>
</tr>
{% endfor %}
Pass the array from Flow as JSON:
{
"items": [
{ "title": "Classic White T-Shirt", "quantity": 2, "price": "29.00" },
{ "title": "Eco Water Bottle", "quantity": 1, "price": "19.00" }
]
}
To preview a loop in the builder, enter the same JSON array as the value in the Test Variables tab.
Objects behave differently in email and HTTP
This is the one sharp edge worth knowing.
- In an HTTP request, interpolating an object or array serialises it to JSON automatically.
{{ variables.records }}becomes a proper JSON array in the request body. Ajsonfilter is also available if you want to be explicit. - In an email, there is no such conversion. Interpolating a whole object renders the literal text
[object Object].
So in emails, always reach into the object rather than printing it:
Wrong: {{ variables.customer }}
Right: {{ variables.customer.firstName }} {{ variables.customer.lastName }}
Or loop over an array instead of printing it whole.
Secrets in templates
Reference a stored secret the same way:
Authorization: Bearer {{ secrets.airtableAccessToken }}
Use the exact syntax {{ secrets.keyName }}. These do not work:
{{ secret.keyName }}- singular{{ secrets['keyName'] }}- bracket syntax
Key names are case-sensitive. See Secrets.
Body override for HTTP requests
The HTTP action's variables field has a second mode. Pass a body key and it replaces the saved request body entirely:
{
"body": { "anything": "you like", "shaped": ["however"] }
}
This is useful when the payload shape is decided by an earlier Flow step. The one exception: if your saved body itself references {{ variables.body }}, it is treated as an ordinary variable instead.
Testing what you wrote
The on-page preview does not evaluate Liquid - it shows the tags as written. To see resolved values:
- For emails, use Send test email from the layout editor.
- For HTTP requests, use the Test button, which shows the Resolved URL and Resolved Body alongside the response.
Related
- Passing data from Shopify Flow - the Variables (JSON) field in depth.
- Build an email layout - where these variables go.
- Secrets - storing credentials safely.
- Make an HTTP request from Shopify Flow - variables in a real request.

