Liquid templating

Cybership uses LiquidJS for label templates. Reference for variables, filters, conditionals, loops, and the formatting helpers available to template authors.

Label text, barcode values, QR values, and table columns accept Liquid expressions. Use the { } picker to insert variables for the selected data source.

Variables

The simplest Liquid expression is a variable substitution. Wrap a variable name in {{ }} and it's replaced by the value at render time:

{{ location_name }}

If the location's name is A-01-02, the rendered output is A-01-02.

Use the { } button next to any text field in the builder to insert a variable from the picker — every option in the picker is guaranteed to resolve for the template's data source.

Dotted paths

Variables can be deep paths, separated by dots. Inside a for loop iterating over a table's row source, you can reach the row's fields directly:

{% for row in product_stock %}
	{{ row.sku }} – {{ row.on_hand }}
{% endfor %}

For the table layer specifically, each column's value template runs inside the loop, so the column references row keys directly without the row. prefix:

{{ sku }}

Filters

Filters transform a value before it's printed. They use the pipe | syntax. Multiple filters can be chained:

{{ generated_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:

{{ generated_at | date: "%m/%d/%y" }}      → 01/15/26
{{ generated_at | date: "%B %-d, %Y" }}    → January 15, 2026
{{ expiration_date | date: "%Y-%m-%d" }}   → 2026-12-31

default

Provide a fallback when a value is empty or null. Note that default does not swallow undefined variables — referencing a variable that doesn't exist on the data source is rejected at save time. Use default for nullable fields:

{{ lot_code | default: "—" }}

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 stock_count > 0 %}IN STOCK{% else %}EMPTY{% endif %}

Conditions support comparison operators (==, !=, <, >, <=, >=), logical operators (and, or), and contains for string/array membership:

{% if location_kind == "STATIC" and total_units > 0 %}STATIC: {{ total_units }}{% endif %}

{% unless %} is the inverse of {% if %} — useful when "not condition" reads better than negating:

{% unless lot_code %}NO LOT{% endunless %}

Loops

{% for %} iterates over an array. Inside the loop the iteration variable is bound to each element:

{% for row in product_stock %}
	{{ row.sku }}: {{ row.on_hand }}
{% endfor %}

For label authors, the most common loop scope is table layers. Set the table's data_path to an array on your payload (e.g. product_stock), and each row's value template renders once per element with the row in scope.

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:

{{ total_units | divided_by: stock_count }}    → average units per SKU
{{ price | times: quantity }}                  → line total
{{ on_hand | minus: 1 }}                       → on-hand minus one

String concatenation uses append / prepend:

{{ location_name | prepend: "Bin " }}    → "Bin A-01-02"
{{ location_name | append: " (active)" }}

Capturing values

{% capture %} stores a rendered fragment in a variable for reuse later in the template:

{% capture display %}{{ location_name }} — {{ warehouse_name }}{% endcapture %}
{{ display | upcase }}

{% assign %} binds a value (or filter chain) to a variable without rendering it inline:

{% assign label = location_barcode | upcase %}
{{ label }}

What's not supported

A few LiquidJS features are intentionally off the table for label templates:

  • Includes / partials — templates can't reference other templates. Each template is fully self-contained.
  • Custom filters / tags — only the LiquidJS standard library is available.
  • HTML / script execution — label templates render to PDF, not HTML. Tags are processed as Liquid; the surrounding text is treated as plain text.

Strict variables at save time

Cybership validates every Liquid expression in your template at save time by rendering it against an example payload for the document's data source. If a template references a variable the data source doesn't define, save / publish is rejected with a message naming the offending path:

Layer "Stock Header" references unknown variable {{ tote_kind }}.

This catches typos and mixed-up data sources (a tote template referencing location_name) before a broken template can ship to print. The variable picker (the { } button next to text fields in the builder) is the safest way to author templates — every variable in the picker is guaranteed to validate.

Further reading