Templates in BxLogistics
An explanation of the different template engines used in BxLogistics
Templates in BxLogistics: Liquid and Bx
BxLogistics builds documents — reports, labels, packing slips, and more — from templates. Every template uses one of two template engines:
- Liquid — a full template language (Shopify Liquid). It lets you build the document: repeat rows, make decisions, reuse parts, group and total data, and pull in extra information.
- Bx — the classic engine. It fills values into your text.
When you create a new template you must choose the engine — there’s no default — and you can change it later.
Templates that render together in one document must use the same engine — Liquid and Bx can’t be mixed in the same document.
What each engine is for
Choose Liquid when the document has any logic: line items, conditional sections, totals, reusable blocks, or data you want to look up while rendering. Liquid keeps that logic in the template, so more can be handled in the template itself, without new queries or code changes.
Choose Bx when the document is simple: fixed text with values dropped in, and no real logic. It’s also the right choice for templates that already exist and work — there’s no need to rewrite them.
What you can do
| Capability | Liquid | Bx |
|---|---|---|
| Insert values and format them (dates, numbers) | Yes | Yes |
| Repeat over line items | Yes, directly in the template | Via a sub-template |
| Show or hide sections conditionally | Yes, anywhere in the content | Limited (one condition per template) |
| Group rows and calculate totals | Yes | No — needs data prepared in advance |
| Sort rows | Yes | Limited |
| Look up extra data while rendering | Yes | No |
| Barcodes | Yes | Yes |
| Reusable building blocks with parameters | Yes | Limited |
| Live preview before saving | Yes | Yes |
Liquid in practice
A few examples of what Liquid makes easy. The full language reference is Shopify’s Liquid documentation: https://shopify.github.io/liquid/
Insert and format a value
Order {{ orderno }} — delivered {{ deliverydate | format: "dd.MM.yyyy" }}
Repeat over line items
{% for line in lines %} {{ line.productname }} — {{ line.quantity }} {{ line.unitname }} {% endfor %}
Show a section only when it applies
{% if packages == 0 %}No packages registered — check the pick.{% endif %}
Group and total — here, sum quantity per product:
{% assign products = lines | map: "productno" | uniq %} {% for product in products %} {{ product }}: {{ lines | where: "productno", product | sum: "quantity" }} {% endfor %}
Barcodes — one filter; leave out the height to use the symbology’s default:
{{ orderno | barcode: "CODE-128" }}
Reuse a block — keep a shared piece in its own template and render it:
{% render "orderline" for lines %} {% render "orderline" for lines as line %} {% render "footer", title: "Total" %}
With for, the block renders once per row; inside it you have forloop.index, first, and last. Use as to name the row, and pass extra values as named arguments (title: "Total").
Pull in data — look up rows while rendering, instead of preparing them up front:
{% assign lines = "packing_lines" | sql: orderno: orderno %}
Example: a Liquid packing slip
<h1>Packing slip {{ orderno }}</h1> <div>{{ orderno | barcode: "CODE-128", height: 30 }}</div> <p> Deliver to: {{ delname }}, {{ deladdress }}, {{ delpostcode }} {{ delcity }}<br> Customer: {{ customerno }} – {{ customername }}<br> Delivery date: {{ deldate | format: "dd.MM.yyyy" }} </p> <table> <thead> <tr><th>#</th><th>Item</th><th>Description</th><th>Qty</th><th>Unit</th></tr> </thead> <tbody> {% for line in lines %} <tr> <td>{{ forloop.index }}</td> <td>{{ line.productno }}</td> <td>{{ line.productname }}</td> <td>{{ line.quantity | format: "0.##" }}</td> <td>{{ line.unitname }}</td> </tr> {% endfor %} </tbody> <tfoot> <tr><td colspan="5">Total quantity: {{ lines | sum: "quantity" }}</td></tr> </tfoot> </table> {% unless comment == blank %} <p><strong>Note:</strong> {{ comment | raw }}</p> {% endunless %}
Previewing a template
You can preview a template before saving it. The preview renders a draft against sample input and returns the finished document with no side effects — no log entry, no email, and nothing sent to a printer. It works for both engines, and is the fastest way to check a new Liquid template as you build it.
Previews return a PDF by default, or the rendered HTML when you need to inspect the markup.
Moving a template from Bx to Liquid
When converting an existing Bx template, these are the common equivalents:
| Bx | Liquid |
|---|---|
{orderno} |
{{ orderno }} |
{qty:double:0.00} |
{{ qty | format: "0.00" }} |
{deliverydate:datetime:dd.MM.yyyy} |
{{ deliverydate | format: "dd.MM.yyyy" }} |
{orderline} |
{% render "orderline" %} |
{lines.orderline} |
{% render "orderline" for lines %} |
<BxBarcode type="EAN13" height="40">{ean}</BxBarcode> |
{{ ean | barcode: "EAN", height: 40 }} |
[{qty} > 0] (Condition) |
{% if qty > 0 %}…{% endif %} |
One thing to watch: Bx wrote values exactly as they were. Liquid escapes output by default, so where a field is meant to contain HTML (for example a note with <br>), add | raw — see below.
Filter reference: format, barcode, and sql
Alongside the standard Liquid filters, BxLogistics provides three of its own.
format
Formats a number or date with a pattern, using invariant culture (so the result is the same regardless of the server’s regional settings).
{{ qty | format: "0.00" }} {{ deliverydate | format: "dd.MM.yyyy" }}
The pattern is a standard .NET format string. Any value that can’t be formatted is left unchanged. For dates you can also use Liquid’s built-in date filter; format is the general-purpose option for numbers and dates alike.
barcode
Turns a value into an inline barcode (SVG) that renders directly in the document.
{{ ean | barcode: "EAN" }} {{ orderno | barcode: "CODE-128", height: 40, scale: 2, humanreadable: true }} {{ sscc | barcode: "GS1-128", box: true, margin: 2, class: "label-barcode" }}
The value piped in is the data to encode. The first argument is the symbology; the rest are named options:
| Option | Type | Default | Description |
|---|---|---|---|
| (symbology) | text | CODE-128 |
The barcode type. Use the human-readable names from the barcode list (BarcodeMap), e.g. EAN, CODE-128, GS1-128, QR Code. |
height |
number | symbology default | Bar height. When omitted, the symbology’s natural height is used; set it to override. |
scale |
number | 1 |
Overall scale factor. |
humanreadable |
true/false | false |
Print the value as text under the barcode. |
box |
true/false | false |
Draw a border box around the barcode. |
margin |
number | 0 |
Quiet-zone margin around the barcode. |
invert |
true/false | false |
Invert the colours. |
class |
text | (none) | CSS class added to the <svg>. |
style |
text | (none) | Inline CSS added to the <svg>. |
Good to know:
- The result is trusted SVG and is inserted as-is — you do not need
| raw. - Empty or missing input produces nothing (no barcode, no error).
- If the data isn’t valid for the chosen symbology (for example an EAN that’s the wrong length), the document fails with a
barcode:error and no barcode is produced — so preview new barcode templates before rolling them out. - Use this filter, not the Bx
<BxBarcode>tag, in Liquid templates.
sql
Runs a stored query from inside the template and gives you the rows, so a template can fetch the data it needs instead of relying on it being prepared in advance.
{% assign rows = "orderlines" | sql %} {% assign rows = "orderlines" | sql: orderno: 4711 %} {% for row in rows %} {{ row.productname }} — {{ row.quantity }} {% endfor %}
The value piped in is the query name (or a numeric query ID). It returns the result rows as a list, which you assign to a variable and loop over.
Good to know:
- Parameters. The query automatically receives the document’s input values. Any named arguments you pass (
orderno: 4711) are layered on top and win where the names overlap. Queries refer to parameters with{name}placeholders, exactly as stored queries do elsewhere. - Empty results come back as an empty list — check with
{% if rows.size > 0 %}…{% endif %}. - Not for the Condition field. Filters can’t be used in a template’s Condition. When a decision depends on a query, make it in the content instead:
{% assign rows = "eligible" | sql %}{% if rows.size > 0 %}…{% endif %}. - Errors — an unknown or failing query (or a context without database access) fails the document with a Bad Request.
Good to know
-
Text is safe by default. Liquid escapes output so stray characters can’t break the layout. When a field is meant to contain formatting (for example a note with line breaks), add
| rawto that value. - Use the barcode filter, not
<BxBarcode>. The<BxBarcode>tag belongs to Bx and doesn’t work in Liquid. - Don’t mix engines in templates that render together, and avoid loops where one template renders another that renders it back — both are rejected when you save.
References
- Shopify Liquid — the language, tags, and filters: https://shopify.github.io/liquid/
BxLogistics adds a few filters of its own —
format,barcode, andsql. The Shopify docs describe the language; where behaviour ever differs, the engine BxLogistics uses (Fluid) is the source of truth.