Ga naar hoofdinhoud
Versie: 1.x

HTML Templates

HTML templates use a logicless Mustache-style syntax with {{variable}} placeholders. They render client-side in the POS app, which means they work offline — no server connection needed to display or print a receipt.

This is the recommended engine for most users.

Syntax

Variables

Insert data using double curly braces:

<h1>{{store.name}}</h1>
<p>Order #{{order.number}}</p>
<p>Total: {{totals.total_display}}</p>

Use _display variants for pre-formatted currency values (e.g., $12.50 instead of 12.5). See the Receipt Data Reference for all available fields.

Sections (Loops and Conditionals)

Use {{#section}}...{{/section}} to loop over arrays or conditionally show content:

{{#lines}}
<div class="line-item">
<span>{{name}} × {{qty}}</span>
<span>{{line_total_display}}</span>
</div>
{{/lines}}

Inverted Sections

Use {{^section}}...{{/section}} to show content when a value is empty or false:

{{#customer.id}}
<p>Customer: {{customer.name}}</p>
{{/customer.id}}
{{^customer.id}}
<p>Guest checkout</p>
{{/customer.id}}

Localised Labels

Use {{i18n.key}} for translatable labels that adapt to the store's language:

<th>{{i18n.subtotal}}</th>
<td>{{totals.subtotal_display}}</td>

Common Patterns

Store Header

The store.address_lines array is pre-formatted for receipts — loop it instead of stitching individual address fields together.

<div style="text-align: center;">
{{#store.logo}}<img src="{{store.logo}}" alt="{{store.name}}" style="max-width: 200px;" />{{/store.logo}}
<h1 style="margin: 8px 0;">{{store.name}}</h1>
{{#store.address_lines}}<div>{{.}}</div>{{/store.address_lines}}
{{#store.phone}}<div>{{store.phone}}</div>{{/store.phone}}
{{#store.tax_ids}}<div>{{#label}}{{label}} {{/label}}{{value}}</div>{{/store.tax_ids}}
</div>

Line Items with Discounts

A line item's discounts field is the discount amount as a positive number, or 0 when there's no discount. Mustache treats 0 as falsy, so {{#discounts}}...{{/discounts}} is the right guard.

{{#lines}}
<div class="line-item" style="display: flex; justify-content: space-between;">
<div>
<div>{{name}} × {{qty}}</div>
{{#discounts}}
<div style="font-size: 0.9em; color: #6b7280;">
<s>{{unit_subtotal_display}}</s> {{unit_price_display}}
</div>
{{/discounts}}
</div>
<div>{{line_total_display}}</div>
</div>
{{/lines}}

Tax Summary

{{#tax_summary}}
<div class="tax-line">
<span>{{label}} ({{rate}}%)</span>
<span>{{tax_amount_display}}</span>
</div>
{{/tax_summary}}

Payment Details

{{#payments}}
<div class="payment" style="display: flex; justify-content: space-between;">
<span>{{method_title}}</span>
<span>{{amount_display}}</span>
</div>
{{#tendered}}
<div style="display: flex; justify-content: space-between;">
<span>{{i18n.tendered}}</span><span>{{tendered_display}}</span>
</div>
<div style="display: flex; justify-content: space-between;">
<span>{{i18n.change}}</span><span>{{change_display}}</span>
</div>
{{/tendered}}
{{/payments}}

Barcodes and QR Codes

Use the <barcode> element — the same syntax as thermal templates. The value goes inside the element; the symbology is set on the type attribute. The renderer replaces each element with an inline SVG.

<!-- Code 128 barcode of the order number -->
<barcode type="code128" height="40">{{order.number}}</barcode>

<!-- QR code -->
<barcode type="qrcode" scale="3">{{order.payment_url}}</barcode>

<!-- EAN-13 -->
<barcode type="ean13">{{order.number}}</barcode>

The type attribute must be set literally in the template — it can't come from a placeholder. A type of qr or qrcode renders as a QR code in both HTML and thermal templates.

If the selected barcode type can't encode the value (for example non-numeric or wrong-length EAN-13 data), the preview shows a Barcode error or QR code error block with the original value, so you can fix the value or switch to a compatible type.

All bwip-js symbologies are supported — code128, qrcode, ean13, ean8, upca, pdf417, datamatrix, and many more.

Styling

Logicless templates are sanitised through WordPress's wp_kses_post before rendering, which strips <style>, <head>, and document-level tags. Use inline styles on each element:

<div style="font-family: monospace; font-size: 12px; max-width: 300px; margin: 0 auto;">
<div style="text-align: center; margin-bottom: 16px;">
<strong style="font-size: 16px;">{{store.name}}</strong>
</div>
<div style="display: flex; justify-content: space-between; font-weight: 700; border-top: 1px solid black; padding-top: 8px;">
<span>{{i18n.total}}</span>
<span>{{totals.total_display}}</span>
</div>
</div>

The same restriction applies to <script>, <link>, and other non-content tags — they're stripped, so don't rely on external CSS or JavaScript.

Designed for black and white

Receipts are usually printed on black-and-white thermal or laser printers. Design with that in mind — use font weight, whitespace, type contrast, and horizontal rules for hierarchy rather than coloured fills. Coloured backgrounds and grey text either disappear on B&W output or print as muddy halftones.

Need a thermal-paper look without a thermal printer?

Click Use Template on the Narrow Receipt card in the gallery — it's a monospace, 72mm-wide layout that prints cleanly on browser print, HTML-capable thermal printers (Sunmi/Imin WebView devices), and standard paper. Not for raw ESC/POS thermal printers — use the Simple Thermal Receipt XML templates for those.

Best Practices

  • Use _display fields for all currency values — they handle locale-aware formatting automatically
  • Use {{i18n.*}} labels instead of hardcoding text so templates work across languages
  • Use sections for conditionals — wrap optional fields in {{#field}}...{{/field}} so they're hidden when empty
  • Start from a gallery template rather than building from scratch
  • Test with the preview in the template editor before activating