Printing
Keel supports direct printing to thermal label printers and standard document printers. Print shipping labels after picking, generate packing slips on demand, or print receipts at point of sale.
Printing works through QZ Tray, a small desktop app that connects your browser to local printers.
Setting up QZ Tray
QZ Tray must be installed on every workstation that needs to print.
- Download from qz.io/download (opens in a new tab)
- Run the installer for your platform
- Launch QZ Tray (it runs in your system tray)
QZ Tray starts automatically on login.
Connecting printers
Open the Console and go to Settings > Devices. The Console automatically detects QZ Tray and shows available printers.
Default printer
Select your default printer from the dropdown. This printer is used when no specific printer is specified in a print job. Click Test print to verify the connection.
Printer mappings
For larger deployments, define logical printer names in your flows (like "Shipping Label Printer") and map them to physical printers at each workstation. This lets different stations use different hardware while running the same flows.
Mappings appear in Settings > Devices when you have a local project running. If a print job requests an unmapped printer, it falls back to the default.
Printing from flows
Use the interactive.print component to send print jobs:
ctx.ui.interactive.print({
title: "Shipping Label",
jobs: [
{
type: "zpl",
data: zplContent,
printer: "Shipping Label Printer",
}
],
autoPrint: true,
});Set autoPrint: true to print immediately when the step loads. Without it, users click a button to trigger the job.
ZPL labels
ZPL (Zebra Programming Language) is the standard for thermal label printers. Most industrial printers from Zebra, Honeywell, and SATO support it.
ctx.ui.interactive.print({
jobs: [
{
type: "zpl",
data: `^XA
^FO50,50^ADN,36,20^FD${order.reference}^FS
^FO50,100^BCN,100,Y,N,N^FD${order.trackingNumber}^FS
^XZ`,
printer: "Shipping Label Printer",
}
],
});The ZPL playground in Settings > Devices lets you test commands and use pre-built templates before adding them to flows.
PDF documents
Print PDF content for pick lists, packing slips, or invoices:
ctx.ui.interactive.print({
jobs: [
{
type: "rawPdf",
data: pdfBase64Content,
printer: "Document Printer",
pageWidth: 1200, // Width in dots
pageHeight: 1800, // Height in dots
dpi: 300,
}
],
});For a 4x6 inch label at 300 DPI: width = 4 × 300 = 1200 dots, height = 6 × 300 = 1800 dots.
Remote files
Print from URLs that are downloaded before printing:
ctx.ui.interactive.print({
jobs: [
{
type: "zpl",
url: "https://api.carrier.com/labels/12345.zpl",
printer: "Shipping Label Printer",
}
],
});Print queue
Every print job is tracked in a queue. Click the menu on the print component to see job status:
| Status | Meaning |
|---|---|
| Pending | Queued, waiting to send |
| Printing | Sent to printer |
| Complete | Finished successfully |
| Error | Failed (printer offline, out of paper, etc.) |
Troubleshooting
QZ Tray won't connect — Check it's running in your system tray. Try restarting it.
Printer not in list — The printer must be installed in your OS first. Check your system's printer settings.
Jobs stuck in queue — Check the physical printer for errors (out of labels, paper jam). Clear any stuck jobs in your OS print queue.
Labels printing wrong — Verify label dimensions match your actual labels. Run calibration from the ZPL playground.
Example: Shipping label
ctx.ui.interactive.print({
title: "Shipping Label",
description: `Print label for ${order.reference}`,
jobs: [
{
type: "zpl",
data: `^XA
^PW812
^LL1200
^FO50,50^A0N,40,40^FDYour Company^FS
^FO50,100^A0N,25,25^FD123 Warehouse Lane^FS
^FO50,200^A0N,35,35^FD${shipment.recipientName}^FS
^FO50,245^A0N,30,30^FD${shipment.addressLine1}^FS
^FO50,285^A0N,30,30^FD${shipment.city}, ${shipment.postalCode}^FS
^FO50,400^BY3,2,150^BCN,150,Y,N,N^FD${shipment.trackingNumber}^FS
^XZ`,
printer: "Shipping Label Printer",
}
],
autoPrint: true,
allowReprint: true,
});