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, conditionalsections, 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 thetemplate 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 existand 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 isShopify'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:
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").
{% render "orderline" for lines %}
{% render "orderline" for lines as line %}
{% render "footer", 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>. |
- 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.
-
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 | raw to 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/