Send ZPL to Zebra and ZPL-compatible label printers from Node.js. Supports persistent TCP connections, batch printing, status queries, browser printing, and a built-in HTTP/WebSocket proxy for web apps.
Transport scope: this package is TCP-focused (
host/port). For USB (--printer usb) and serial/Bluetooth SPP (--serial), use thezplCLI or the Rustzpl_toolchain_print_clientAPI.
Part of the zpl-toolchain project.
npm install @zpl-toolchain/printimport { print, TcpPrinter } from "@zpl-toolchain/print";
// One-shot: send ZPL and disconnect
const result = await print("^XA^FDHello^FS^XZ", { host: "192.168.1.55" });
console.log(result); // { success: true, bytesWritten: 21 }
// Persistent connection: reuse for multiple labels
const printer = new TcpPrinter({ host: "192.168.1.55" });
await printer.print("^XA^FDLabel 1^FS^XZ");
await printer.print("^XA^FDLabel 2^FS^XZ");
// Check if a printer is reachable
if (await printer.isReachable()) {
console.log("Printer is online");
}
// Query printer status
const status = await printer.getStatus();
console.log(`Paper out: ${status.paperOut}, Paused: ${status.paused}`);
await printer.close();- One-shot printing —
print()sends ZPL and disconnects in a single call - Persistent connections —
TcpPrinterkeeps the socket open for high-throughput printing - Batch printing —
TcpPrinter.printBatch()sends multiple labels with optional progress callbacks and status polling - Status queries —
TcpPrinter.getStatus()parses the full~HShost status response (24 fields) - Retry with backoff — configurable automatic retries on transient errors
- Abort support —
AbortSignalsupport on one-shot and batch/completion flows - Validated printing —
printValidated()validates ZPL before sending (requires optional@zpl-toolchain/corepeer dependency) - HTTP/WebSocket proxy —
createPrintProxy()bridges browser apps to network printers with CORS, SSRF protection, allowlists, connection limits, optional per-client WS rate limiting, and correlation IDs - Browser printing —
ZebraBrowserPrintwraps the Zebra Browser Print agent for printing from the browser viafetch() - Full TypeScript types — all APIs are fully typed with exported interfaces
| Function | Description |
|---|---|
print(zpl, config) |
Send ZPL and disconnect |
printValidated(zpl, config, opts?) |
Validate then print (requires @zpl-toolchain/core) |
createPrinter(config) |
Convenience factory for new TcpPrinter(config) |
| Method | Description |
|---|---|
new TcpPrinter(config) |
Create a printer (connects lazily on first call) |
print(zpl, opts?) |
Send ZPL over the persistent connection |
isReachable() |
Check if the printer accepts TCP connections |
getStatus(opts?) |
Query ~HS host status (paper out, paused, labels remaining, etc.); supports AbortSignal |
query(command, opts?) |
Send an arbitrary command (e.g. ~HI) and return raw response; supports AbortSignal |
printBatch(labels, opts?, onProgress?) |
Send multiple labels with optional status polling and progress callbacks |
waitForCompletion(pollInterval?, timeoutMs?, signal?) |
Poll until all labels are printed, timeout, or abort |
close() |
Gracefully close the connection |
parseHostStatus() now uses strict semantics by default:
- requires exactly 3
STX/ETXframes for~HS - requires expected field counts per line
- throws on malformed or truncated responses
Use explicit helpers when you need mode control:
import {
parseHostStatus, // strict default
parseHostStatusStrict, // explicit strict
parseHostStatusLenient, // best-effort defaults
} from "@zpl-toolchain/print";Lenient mode is useful for dashboards or degraded environments where partial responses are acceptable.
import { createPrintProxy } from "@zpl-toolchain/print/proxy";
const server = createPrintProxy({
port: 3001,
allowedPrinters: ["192.168.1.*", "printer.local"],
allowedPorts: [9100],
wsRateLimitPerClient: { maxRequests: 10, windowMs: 1000 },
cors: "*",
});
// POST /print { printer, port?, zpl }
// POST /status { printer, port? }
// WebSocket: send { id?, type: "print"|"status", printer, port?, zpl? }
// WebSocket responses echo id when provided.import { ZebraBrowserPrint } from "@zpl-toolchain/print/browser";
const zbp = new ZebraBrowserPrint();
if (await zbp.isAvailable()) {
const printers = await zbp.discover();
await zbp.print(printers[0], "^XA^FDHello^FS^XZ");
}All types are exported from the main entry point:
PrinterConfig— host, port, timeout, retries,signalPrintResult— success, bytesWritten, error detailsPrinterStatus— 24-field parsed~HSresponsePrintError— typed error withcode(CONNECTION_REFUSED, TIMEOUT, etc.)BatchOptions/BatchProgress/BatchResult— batch printing control (includingjobId,phase, and partial error details whereerror.indexis the 0-based failed label index)JobId/JobPhase/createJobId()— job lifecycle for correlation and deterministic completion (see Print Client Guide)ProxyConfig— proxy server configurationValidateOptions— options for validated printing
For known framed Zebra commands:
~HSexpects 3STX/ETXframes~HIexpects 1STX/ETXframe
For these commands, tcpQuery() uses strict byte-level frame parsing and fails on truncated/wrong-frame-count responses. Unknown commands fall back to idle-based completion.
- Node.js 18+ (uses
node:netfor TCP) - Optional:
@zpl-toolchain/corepeer dependency forprintValidated()
See the Print Client Guide for comprehensive usage, CLI integration, proxy setup, and troubleshooting.
Dual-licensed under MIT or Apache-2.0.
