Label Printing
Muxit prints labels through the printer driver that is already installed on the computer, so the same connector works for a Brother P-touch tape printer, an Epson TM-C3500 colour label printer, a Zebra with its Windows driver, a PDF printer for testing, or a CUPS queue on Linux. Labels are JSON templates with {{placeholders}}; scripts, dashboards, MCP clients and the AI fill them in.
This guide walks through a production-line setup. Reference pages: LabelPrinter driver and Label Templates.
Pro feature
Printing to a real printer needs the Label Printing feature (Pro). Everything else — listing printers, creating connectors, editing templates, live previews, and dry runs on the null backend — works on every tier, so you can set the line up completely before upgrading.
1. Open Tools → Label Printing
Install the manufacturer's driver and print a test page from the operating system first — Muxit uses that driver as-is. Then open Tools → Label Printing in the title bar. The page has two tabs:
- Printers lists every printer the operating system knows, with its status (ready, paper-out, offline, …), resolutions, the paper sizes its driver defines, and the connector(s) already attached to it. Click Create connector… on a printer: pick a connector name, the template it should print by default, optionally one of the driver's paper sizes (Brother and Epson prefer their own) and the label stock size. Muxit writes
workspace/connectors/<name>.js, enables it and reloads — the connector appears below as a card with its live status, a template dropdown, a Test print button and Open file for the generated config. - Templates shows the label templates in
workspace/labels/. Select one, fill in sample values for its fields and watch the preview update; Print sends it to any printer connector. New template… creates a starter file and opens it in the editor; Open in editor edits the JSON, and the list and preview follow your saves.
Everything the page does is also available to scripts through the connector it created:
const printer = connector("brother"); // the name you chose in the dialog
printer.template = "asset-tag"; // change the default template at runtime
printer.print({ name: "Oscilloscope" }); // data only — the template property is used
printer.status; // "ready" | "paper-out" | "offline" | …
printer.listPrinters(); // same list as the Printers tabPrefer the file? The starter workspace/connectors/_examples/label-printer.js is the same thing the dialog writes; set printer: to a substring of the printer name, template: to the default template, and on Windows paper: to one of the driver's sizes.
Test without hardware
Set backend: "null" (or printer: "PDF" with Microsoft Print to PDF) while designing templates. The null backend records jobs and reports ready, so scripts, dashboards and the jobs stream behave exactly as with a real printer — nothing is printed.
Brother P-touch (PT-9700PC and similar)
- The tape width is the paper width; the label length is the paper height. In Printing Preferences the driver lists sizes such as
24mm(continuous) or fixed lengths, and lets you define your own (Paper Size Setup). Match the template: a 24 mm tape with 90 mm labels issize: { width: 24, height: 90 }— orwidth: 90, height: 24with"orientation": "landscape"if you design the label along the tape. - Set
paper:to the driver's size name ("24mm") rather than relying on a custom size; Brother drivers ignore custom sizes they have not defined. - The printable width is narrower than the tape (roughly 32 mm on a 36 mm tape). Keep 1–2 mm of side margin.
- Auto-cut, half-cut and "print then cut at end" are driver preferences; Muxit does not control the cutter.
- Resolution is 360 dpi; leave
dpi: 0so the driver renders at that.
Epson TM-C3500
- Define your media once in the driver's Media Settings (die-cut / continuous / black mark, width, length, gap) and save it as a user-defined size. Refer to it with
paper: "<that name>". A mismatch between the defined media and the loaded roll produces a media-form error on the printer, not in Windows. - The printer is 360 dpi (720 × 360 for high quality — pick it through the driver's defaults if you want it). Colour is fully supported:
"color": "#c00000"on text, colouredfillon rectangles, colour images. - Media presence and cover state show up in
status/statusDetail(paper-out,door open).
Zebra, TSC and other ZPL/TSPL printers
With the manufacturer's Windows driver installed they behave like any other printer. Alternatively send the printer's own language with printRaw() — the driver then does not render anything, so the printer's own barcode commands do the work. printRaw() is still a print call: it needs the Label Printing feature (Pro) like print() does, and it is exempt only on the null dry-run backend:
connector("label-printer").printRaw({ data: "^XA^FO50,50^BCN,80,Y,N,N^FD{{serial}}^FS^XZ".replace("{{serial}}", serial) });A raw network backend (backend: "tcp", host, port: 9100) exists for printRaw(); rendered labels over TCP need a label-language encoder and are not supported yet.
Linux / macOS (CUPS)
The cups backend uses lp and lpstat. Add the printer with the system's printer settings or lpadmin, check lpstat -p, and Muxit picks it up. Status detection is heuristic (media-empty, offline, disabled reasons) and job tracking is by queue membership — good enough for alerts, less detailed than Windows.
2. Design the label
Templates are workspace/labels/<name>.label.json files. Two examples are seeded: asset-tag (text only) and shipping (Code 128 + QR). On the Templates tab, New template… writes a starter and opens it in the visual designer; Design opens an existing template the same way and Edit JSON opens the raw file in the editor. Both are tabs on the same file — the Design / Edit JSON buttons in the tab's toolbar switch between them, and edits carry over.
The designer is a page-sized canvas with the label rendered by the server underneath (the same renderer that prints, so what you see is what the printer gets):
- Add elements by clicking or dragging them from the palette on the left: text, barcode, QR code, image, rectangle, line. Drop an image file from the Explorer to add it — images must live in
workspace/labels/. - Move / resize on the canvas (handles on the selected element; line endpoints drag individually). Positions snap to 0.5 mm — hold Alt to place freely, toggle Grid, Snap and Outlines in the toolbar. Rotated elements can be moved and edited in the panel but not resized on the canvas.
- Properties on the right: coordinates, text and font, symbology, colours,
whenconditions … With nothing selected the panel shows the label itself (size, unit, dpi, background, default font) and the fields table (name, required, default). - Sample data below the properties fills the placeholders for the preview; values are remembered per template and shared with the Templates tab.
- Problems appear in the strip at the bottom (missing required fields, a barcode box too small, an unknown image …); click one to select the offending element.
- Keyboard: arrows nudge 0.1 mm (Shift: 1 mm), Delete removes, Ctrl+D duplicates, Ctrl+Z / Ctrl+Y undo / redo, Esc deselects, Ctrl+S saves.
Saving writes the same JSON described in Label Templates — the designer normalises aliases and drops default values, so the first save of a hand-written file may reorder keys. Files with comments or in sub-folders open as plain JSON. From a script, preview({ product: "Widget", serial: "SN-000123" }) returns the same PNG, and validateTemplate("shipping") lists errors and warnings without rendering.
The optional Label Printer dashboard (workspace/dashboards/label-printer.dashboard.json) shows the last rendered label and printer status on a dashboard; it is an example, not a requirement.
3. Trigger prints from the line
Everything that can call a connector action can print a label:
| Trigger | How |
|---|---|
| Barcode scanner on a serial port | A LineText connector for the scanner; a script subscribes to its output stream and calls print() |
| PLC / MES over MQTT | MqttBridge connector; a script subscribes to stream:mqtt:message on your topic |
| Another application | WebSocket connector.call (see the WebSocket API) or the JavaScript library |
| Claude / MCP clients | The call_action MCP tool — the AI sees the template fields via listTemplates() |
| Operator | The Templates tab, a dashboard button bound to label-printer.print (prints the current template), or a connector method with fixed data |
workspace/scripts/examples/label-on-scan.js combines the first two with alerting:
const printer = connector("label-printer");
printer.template = "shipping";
on("stream:scanner:output", (line) => {
printer.print({ product: "Widget", serial: String(line).trim() });
});
on("stream:label-printer:jobs", (evt) => {
if (evt.event === "failed" || evt.event === "stalled")
connector("webhook").notify("Label job " + evt.event, `${evt.jobId}: ${evt.error}`, "error");
});
while (true) {
if (printer.status === "paper-out") connector("webhook").notify("Labels empty", printer.statusDetail, "warn");
delay(5000);
}Serial numbers: {{sequence}} increments once per printed label and survives restarts (stored under workspace/config/drivers/<connector>/). Set the starting value with printer.sequence = 1000. Prefer values from your MES when you have them — the sequence is for stand-alone lines.
Each print() or printBatch() claims its range of values when the job is queued, so two callers printing at the same moment — a script and a dashboard button, say — always get different serials. A job that is then rejected (queue full, minIntervalMs) or fails to spool keeps the range it claimed, so the printed serials can have gaps. That is deliberate: a gap is a question the job log answers, whereas two labels carrying the same serial is the thing a serial number exists to prevent.
Batches: printBatch({ template, items: [...] }) spools one multi-page job, which is much faster on the TM-C3500 than many single jobs and keeps the order intact.
4. Monitor and protect the line
Status. status is one of ready, printing, paper-out, offline, paused, error, unknown, refreshed every few seconds from the spooler; statusDetail carries the driver's words (paper out, door open). Bind an indicator widget or poll it from a script.
Job outcomes. The jobs stream reports queued → printing → printed for each job, failed when the spooler refused it, stalled when the spooler is waiting on the printer (paper out, offline — once per job, followed by printing when it resumes) and cancelled. jobsPrinted / jobsFailed / lastJob / lastError summarise the same information for dashboards.
Runaway protection. A script bug that loops over print() can empty a roll in minutes. The driver enforces its own caps on every tier:
| Config | Default | Effect |
|---|---|---|
maxCopies | 50 | Copies per call |
maxBatch | 500 | Items per printBatch() |
maxQueue | 20 | Jobs allowed to wait — further calls fail instead of piling up |
minIntervalMs | 0 | Minimum time between calls |
Set them to what one shift can plausibly need (the example connector uses 20 / 200 / 20). On the Lab tier the per-connector safety: block adds rate limits and confirmations (confirm: { printBatch: "always" }); at the global Observe safety level all printing is simulated. cancelAll() clears both the driver queue and the spooler.
AI. The starter connector's ai.instructions tell the assistant that printing consumes media and to prefer preview(). Keep that in place if operators talk to the line through chat.
Troubleshooting
| Symptom | Check |
|---|---|
status is offline right after enabling | statusDetail lists the installed printers — fix printer: to match one of them |
| Label prints tiny in a corner / wrong orientation (Brother) | Use paper: "<driver size>" and set the tape width in Printing Preferences; try "orientation": "landscape" |
| Media-form error on the TM-C3500 | The driver's defined media does not match the loaded roll |
| Barcode does not scan | Enlarge the box (the error message says the minimum), keep the default quiet zone, render at the printer's dpi (dpi: 0), avoid shrink-style scaling |
LICENSE_LIMIT on print | Printing to a real printer is a Pro feature (Label Printing); previews and backend: "null" dry runs stay available |
| The Printers tab shows "enumeration is taking longer than 20 s" | An offline network printer is stalling the Windows spooler; the last known list is shown as stale — remove or bring back the queue, then Refresh |
print() times out with "not accepted by the spooler" | The spooler is blocked (offline printer, paused queue); the job stays queued — look at statusDetail, then cancelAll() |
| Nothing prints on Linux | lpstat -p must list the printer; install cups-client if lp is missing |