Liquid templating
Cybership uses LiquidJS for automation action fields. Reference for variables, filters, conditionals, loops, and the formatting helpers available to automation authors.
Automation fields with a + variable picker accept Liquid expressions. Use the picker for values from the trigger and earlier actions.
Variables
The simplest Liquid expression is a variable substitution. Wrap a variable name in {{ }} and it's replaced by the value at execution time:
{{ trigger_data.metadata.order.order_number }}If the triggering order's number is 1024, the rendered output is 1024.
Use the + button next to any text field in the automation builder to insert a variable from the picker — every option in the picker resolves against the automation's trigger and the action steps that ran before the current one.
Dotted paths
Variables can be deep paths, separated by dots, walking nested objects and arrays:
{{ trigger_data.metadata.order.shipping_address_city }}
{{ trigger_data.metadata.order.line_items.0.sku }}Step outputs
Every action that runs in an automation emits an output object. Later steps reference those outputs by the step's display label (the name shown in the automation builder), not by the step's internal UUID:
{{ get_fulfillment_order.fulfillment_order_id }}
{{ previous-step.order_id }}Hyphenated labels (previous-step, send-email-1) work the same as underscored ones. Use the step's display label from the picker.
Filters
Filters transform a value before it's printed. They use the pipe | syntax. Multiple filters can be chained:
{{ trigger_data.metadata.order.created_at | date: "%m/%d/%y" | upcase }}Cybership exposes the full LiquidJS standard filter library. The most commonly useful ones are:
date
Format a date string. Uses strftime format codes:
{{ trigger_data.metadata.order.created_at | date: "%m/%d/%y" }} → 01/15/26
{{ trigger_data.metadata.order.created_at | date: "%B %-d, %Y" }} → January 15, 2026
{{ trigger_data.metadata.order.created_at | date: "%Y-%m-%d" }} → 2026-01-15default
Provide a fallback when a value is empty, null, or unresolved:
{{ trigger_data.metadata.order.note | default: "(no note)" }}For case conversion, rounding, strings, arrays, and arithmetic, see the LiquidJS filter reference. Chain filters with |.
Conditionals
Use {% if %} / {% elsif %} / {% else %} / {% endif %} to branch on a value:
{% if trigger_data.metadata.order.total_price > 100 %}High-value order{% else %}Standard order{% endif %}Conditions support comparison operators (==, !=, <, >, <=, >=), logical operators (and, or), and contains for string/array membership:
{% if trigger_data.metadata.order.country == "US" and trigger_data.metadata.order.total_price > 50 %}
Domestic priority order
{% endif %}{% unless %} is the inverse of {% if %} — useful when "not condition" reads better than negating:
{% unless trigger_data.metadata.order.note %}NO NOTE{% endunless %}Loops
{% for %} iterates over an array. Inside the loop the iteration variable is bound to each element. The most common loop in an email body is over the order's line items:
{% for item in trigger_data.metadata.order.line_items %}
{{ item.quantity }}× {{ item.name }} ({{ item.sku }})
{% endfor %}Use forloop.index, forloop.first, forloop.last, or forloop.length for loop position. {% break %} and {% continue %} control iteration.
Math and string operations
Liquid supports basic arithmetic via filters, not operators:
{{ trigger_data.metadata.order.total_price | times: 0.1 }} → 10% of total
{{ items_remaining | minus: 1 }} → countdownString concatenation uses append / prepend:
{{ trigger_data.metadata.order.order_number | prepend: "Order #" }}
{{ trigger_data.metadata.order.shipping_address_name | append: " — VIP" }}Capturing values
{% capture %} stores a rendered fragment in a variable for reuse later in the template:
{% capture greeting %}Hi {{ trigger_data.metadata.order.customer_name | capitalize }}{% endcapture %}
{{ greeting }}, your order has shipped.{% assign %} binds a value (or filter chain) to a variable without rendering it inline:
{% assign first_item = trigger_data.metadata.order.line_items | first %}
{{ first_item.name }}Email body specifics
Email bodies are post-processed after Liquid rendering: every \n (and \r\n) is converted to <br> so newlines you type in the body — or that come back from a Liquid variable — render as line breaks in the delivered HTML email. You don't need to write <br> tags yourself.
Hi {{ trigger_data.metadata.order.customer_name }},
Your order {{ trigger_data.metadata.order.order_number }} is on its way.
{% for item in trigger_data.metadata.order.line_items %}
{{ item.quantity }}× {{ item.name }}
{% endfor %}Missing variables
If a Liquid expression references a variable that doesn't resolve at execution time, the expression renders as an empty string rather than leaving the literal {{ ... }} in the output, and the unresolved path is logged. This means a typo in {{ trigger_data.metadata.order.does_not_exist }} produces an empty slot in your output rather than a malformed-looking template.
The variable picker (the + button next to text fields) is the safest way to author templates: every variable in the picker is exposed by the trigger or a prior step, so you don't have to guess at the path.
What's not supported
A few LiquidJS features are intentionally off the table:
- Includes / partials — action templates can't reference other templates. Each field is fully self-contained.
- Custom filters / tags — only the LiquidJS standard library is available.
- HTML / script execution beyond what the email pipeline accepts — email bodies are delivered as HTML, so standard HTML tags work; they aren't sandboxed by Liquid itself.