Label Templates
A label template is a JSON file in workspace/labels/ named <name>.label.json. The LabelPrinter driver renders it with the data you pass to print() and sends the result to the printer. Scripts can also pass the same object inline (print({ template: { size, elements }, data })) for fully dynamic layouts. Templates reload automatically when the file changes. You do not have to write this by hand: Tools → Label Printing → Templates → Design opens a template in the visual designer, which reads and writes exactly this schema (see the guide).
{
"name": "shipping",
"size": { "width": 62, "height": 100, "unit": "mm" },
"dpi": 300,
"font": "Arial",
"fields": {
"product": { "required": true },
"serial": { "required": true },
"qty": { "default": "1" }
},
"elements": [
{ "type": "text", "x": 4, "y": 4, "w": 54, "h": 12, "text": "{{product}}", "size": 16, "bold": true, "shrink": true },
{ "type": "text", "x": 4, "y": 17, "w": 54, "h": 8, "text": "Qty {{qty}} · {{date|yyyy-MM-dd}} · #{{sequence|pad:6}}", "size": 8 },
{ "type": "barcode", "x": 4, "y": 33, "w": 54, "h": 20, "symbology": "code128", "value": "{{serial}}", "showText": true },
{ "type": "barcode", "x": 4, "y": 57, "w": 30, "h": 30, "symbology": "qr", "value": "https://example.com/t/{{serial}}" },
{ "type": "image", "x": 36, "y": 57, "w": 22, "h": 22, "src": "logo.png", "fit": "contain" },
{ "type": "rect", "x": 1, "y": 1, "w": 60, "h": 98, "strokeWidth": 0.3, "radius": 2 },
{ "type": "line", "x1": 4, "y1": 30, "x2": 58, "y2": 30 }
]
}Page
| Key | Type | Default | Description |
|---|---|---|---|
name | string | file stem | Shown in listTemplates() and the {{template}} placeholder |
size.width, size.height | number | required | Label size in size.unit |
size.unit | string | "mm" | mm | cm | in | pt | px — also the unit for every element coordinate |
dpi | int | 300 | Design resolution: used for px units and as the render resolution when the printer's is unknown (previews, PDF) |
orientation | string | "portrait" | landscape rotates the finished page 90° for stock that feeds the other way (tape printers) |
background | string | "#FFFFFF" | Page colour (#RGB, #RRGGBB, #RRGGBBAA or a CSS name) |
font | string | "Arial" | Default font family for text and barcode captions |
fontSize | number | 10 | Default text size in points |
fields | object | — | Declared data fields (see below) |
elements | array | — | Drawn in order; later elements paint over earlier ones |
Coordinates are measured from the top-left corner; x, y, w, h are in size.unit. Every element also accepts:
| Key | Description |
|---|---|
id | Name used in error messages (defaults to #index) |
rotate | Clockwise rotation in degrees around the element's centre |
when | Placeholder expression; the element is skipped when it expands to empty, false, 0, no or null |
Fields
fields declares what data the template expects. Undeclared placeholders still resolve from data; declaring them gives validation and defaults.
"fields": {
"serial": { "required": true },
"destination": { "default": "" },
"lot": "L0"
}A required field that is missing from data fails print() with VALIDATION_INVALID_VALUE listing every missing name. A field with a default uses it when absent. validateTemplate() warns about placeholders that are not declared and fields that are never used.
Elements
text
| Key | Default | Description |
|---|---|---|
text | — | The text; supports placeholders and \n |
font, size | template defaults | Family and size in points |
bold, italic | false | Weight / slant |
color | #000000 | Text colour |
align | left | left | center | right |
valign | top | top | middle | bottom |
wrap | true | Word-wrap inside the box (long words break by character) |
shrink | false | Reduce the size (down to 4 pt) until the text fits the box |
lineHeight | 1.2 | Line spacing as a multiple of the font size |
Text is clipped to its box. Fonts come from the server's operating system; a family that is not installed falls back to the default sans-serif.
barcode
| Key | Default | Description |
|---|---|---|
symbology | code128 | See the table below (type: "qr" is shorthand for symbology: "qr") |
value | — | Encoded content; supports placeholders |
showText | false | Print the human-readable value under 1D codes |
textSize, font | 7 pt, template font | Caption styling |
color | #000000 | Bar colour |
errorCorrection | per symbology | QR L | M | Q | H; PDF417 0–8; Aztec percentage |
margin | standard quiet zone | Quiet zone in modules per side reserved inside the box (0 uses the whole box) |
| Symbology | Kind | Value rules |
|---|---|---|
code128 | 1D | ASCII 0–127 |
code39 | 1D | A–Z, 0–9, space, - . $ / + % (lower case is upper-cased) |
code93 | 1D | Same set as Code 39 |
ean13 | 1D | 12 digits (13 with a valid check digit) |
ean8 | 1D | 7 digits (8 with check digit) |
upca | 1D | 11 digits (12 with check digit) |
upce | 1D | 7 digits (8 with check digit), starting with 0 or 1 |
itf | 1D | Digits, even count (a leading 0 is added to odd lengths) |
codabar | 1D | Digits and - $ : / . +; start/stop A–D added automatically |
qr | 2D | Any text (UTF-8) |
datamatrix | 2D | Any text |
pdf417 | 2D (stacked) | Any text |
aztec | 2D | Any text |
Modules are scaled by a whole number of device pixels and drawn without anti-aliasing so they scan reliably at 203–360 dpi. The code is centred in its box; 1D codes stretch to the box height (minus the caption). If the box is too small for one pixel per module, print() fails with a message that says how large the box needs to be at the printer's dpi. Standard quiet zones are reserved by default — 10 modules for 1D codes, 4 for QR, 2 for Data Matrix / PDF417, 1 for Aztec — because scanners reject symbols without them (UPC/EAN end guards in particular).
TIP
Barcodes render in previews on every tier. Printing them (like any label) to a real printer needs the Label Printing feature (Pro).
image
| Key | Default | Description |
|---|---|---|
src | — | File name relative to workspace/labels/ (PNG, JPEG, BMP, WebP …), a data:image/…;base64, URL, or a placeholder whose data value is base64 (a snapshot() from a camera) |
fit | contain | contain (keep aspect, fit inside) | cover (keep aspect, fill and crop) | stretch |
File images must live inside the templates directory; paths that escape it are rejected.
rect
| Key | Default | Description |
|---|---|---|
fill | none | Fill colour |
stroke | #000000 | Outline colour (null/"none" for no outline) |
strokeWidth | 0.3 | Outline width in template units, drawn inside the box |
radius | 0 | Corner radius in template units |
line
x1, y1, x2, y2 in template units, plus stroke (#000000) and strokeWidth (0.3).
Placeholders
{{name}} anywhere in text, value, src or when is replaced at print time. Look-up order: data (exact, then case-insensitive), dotted paths into nested objects ({{order.id}}), built-ins, then the field's default.
| Built-in | Value |
|---|---|
{{sequence}} | The connector's running counter; increments per printed label (not for previews) and persists across restarts. Set it with the sequence property |
{{date}}, {{time}}, {{now}} | Render time — combine with a format: `{{now |
{{copy}} | Copy number (1-based) |
{{page}}, {{pages}} | Position within a printBatch() |
{{template}}, {{printer}} | Template name and resolved printer name |
Formatters chain with |:
| Formatter | Example | Result |
|---|---|---|
pad:N | `{{sequence | pad:6}}` |
padright:N | `{{name | padright:10}}` |
upper, lower, trim | `{{name | trim |
substr:start,len | `{{serial | substr:0,4}}` |
default:value | `{{lot | default:n/a}}` |
date:format | `{{expires | date:dd.MM.yyyy}}` |
| any .NET format string | `{{qty | 000}}, {{now |
Numbers print without trailing zeros; arrays and objects print as JSON.
Limits
Bounds that fail at validation (or refuse a single element) rather than at allocation:
| What | Limit |
|---|---|
Page width / height | 2000 mm each |
fontSize and a text element's size | 500 pt |
Image (inline data: URL, bare base64 or file) | 8 MB encoded, 16 megapixels decoded — checked from the header before decoding |
pad:N / padright:N | N is capped at 256 |
| Placeholder parsing | 250 ms; an unterminated {{ … with a very long formatter chain is reported as a template error |
labels.preview over WebSocket | 5 per second per connection (security.rateLimit.previewsPerSec, minimum 1) |
Sizing tips
- Design at the printer's real resolution: Brother P-touch 360 dpi, Epson TM-C3500 360 dpi, most Zebra 203 or 300 dpi. The driver renders at that dpi automatically;
dpiin the template only matters forpxunits and previews. - Keep 1–2 mm of margin: printer drivers have an unprintable border and tape printers cut a little off each end.
- A 1D code needs roughly
modules × 2device pixels of width to be scannable at arm's length; when in doubt make the box wider rather than taller. - Use
preview()(or the dashboard canvas bound to thepreviewstream) while iterating — it does not consume media or the sequence counter. validateTemplate()reports errors and warnings without rendering;listTemplates()shows files that failed to parse.