Skip to content

XanderLuciano/zebra-label-printer

Repository files navigation

🦓 Zebra Label Printer

Print labels on Zebra GK420d printers from any device on your network. Text, barcodes, QR codes, serial numbers — via web dashboard, REST API, or CLI.

# One-command install (always latest)
curl -fsSL https://raw.githubusercontent.com/XanderLuciano/zebra-label-printer/main/install.sh | bash

# Print a label
curl -X POST http://localhost:3420/api/print/text \
  -H "Content-Type: application/json" \
  -d '{"lines":["Hello World"]}'

# Open the web dashboard
open http://localhost:3420

Quick Install

Method Command
One-liner (Linux/Mac) curl -fsSL https://raw.githubusercontent.com/XanderLuciano/zebra-label-printer/main/install.sh | bash
From source git clonebash build.shnode dist/server/index.js
Docker docker compose up -d
npm global npm install -g zebra-label-printer && zebra-label serve

Everything runs on port 3420: web dashboard at /, API at /api/*, docs at /api/docs.

Features

  • Web dashboard — Nuxt 4 UI with printer status, quick print, history, queue management, debug info, settings
  • Persistent job queue — SQLite-backed, survives reboots, auto-retries when printer reconnects
  • Auto-recovery — CUPS auto-re-enable on USB disconnect/reconnect
  • Serial number printing — batch print with auto-incrementing {serial} placeholder
  • Label size management — 6 standard sizes + custom, recent sizes tracked for hot-swapping
  • Zod validation — all endpoints validated, structured 400s with field-level errors
  • OpenAPI docs — interactive Swagger UI at /api/docs
  • ZPL builder — fluent TypeScript API for text, 8 barcode types, QR codes, Data Matrix
  • CLI toolzebra-label print-text, zebra-label print-bc, zebra-label discover
  • Zero-config discovery — auto-finds Zebra printers via CUPS

CLI

zebra-label discover        # List printers
zebra-label print-test       # Test label (text + barcode + QR)
zebra-label print-text "Hi"  # Quick text label
zebra-label print-bc "SKU-123" "Widget"  # Barcode label
zebra-label print-qr "https://example.com" "Scan"  # QR code
zebra-label serve            # Start API + web UI

Docker

docker-compose up -d
# → http://localhost:3420

Development

# API server
npx tsx src/server/index.ts    # → :3420

# UI dev (optional — already bundled in API server)
cd web && npm install && npm run dev   # → :3000

API Reference

All endpoints accept JSON. All POST bodies are validated with Zod — invalid requests get structured 400s with field-level error details.

For full interactive docs, start the server and open http://localhost:3420/api/docs (Swagger UI).

Method Endpoint Description
GET /api/health Health check
GET /api/printers List available printers
GET /api/docs Swagger UI
GET /api/docs/openapi.json OpenAPI 3.1 spec
GET /api/jobs List print jobs (filterable by status)
GET /api/jobs/stats Job counts by status
GET /api/jobs/:id Job detail with event log
POST /api/jobs/:id/cancel Cancel a pending job
GET /api/debug System diagnostics (printer, queue, DB, server)
GET /api/settings Get all settings
PUT /api/settings Update settings
GET /api/label-size Current label size + recent sizes + standard sizes
PUT /api/label-size Set label dimensions
POST /api/print/text Print text label
POST /api/print/barcode Print barcode label
POST /api/print/qr Print QR code label
POST /api/print/zpl Print raw ZPL (text/plain or JSON)
POST /api/print/label Print composed label from elements

Examples:

# Health check
curl http://localhost:3420/api/health

# Text label
curl -X POST http://localhost:3420/api/print/text \
  -H "Content-Type: application/json" \
  -d '{"lines": ["Living Room", "Box #3"]}'

# Barcode
curl -X POST http://localhost:3420/api/print/barcode \
  -H "Content-Type: application/json" \
  -d '{"data": "INV-42069", "text": "Inventory Tag"}'

# Get/set label size
curl http://localhost:3420/api/label-size
curl -X PUT http://localhost:3420/api/label-size \
  -H "Content-Type: application/json" \
  -d '{"widthDots": 609, "heightDots": 406, "name": "3×2\" Shipping"}'

# Print a part label (QR code + part info, 2x1" layout)
curl -X POST http://localhost:3420/api/print/label \
  -H "Content-Type: application/json" \
  -d '{
    "elements": [
      {"type": "qrcode", "content": "135853-002-A-NRG", "options": {"x": 40, "y": 50, "magnification": 4}},
      {"type": "text", "content": "FTS Lens Mount", "options": {"x": 160, "y": 50, "height": 35, "width": 28}},
      {"type": "text", "content": "135853-002", "options": {"x": 160, "y": 95, "height": 30, "width": 28}},
      {"type": "text", "content": "Rev A | NRG", "options": {"x": 160, "y": 135, "height": 25, "width": 20}}
    ]
  }'

# Print 5 copies of a part label (one label per part)
curl -X POST http://localhost:3420/api/print/label \
  -H "Content-Type: application/json" \
  -d '{
    "elements": [
      {"type": "qrcode", "content": "135853-002-A-NRG", "options": {"x": 40, "y": 50, "magnification": 4}},
      {"type": "text", "content": "FTS Lens Mount", "options": {"x": 160, "y": 50, "height": 35, "width": 28}},
      {"type": "text", "content": "135853-002", "options": {"x": 160, "y": 95, "height": 30, "width": 28}},
      {"type": "text", "content": "Rev A | NRG", "options": {"x": 160, "y": 135, "height": 25, "width": 20}},
      {"type": "raw", "zpl": "^PQ5"}
    ]
  }'

# Validation errors return structured details:
curl -X POST http://localhost:3420/api/print/text \
  -H "Content-Type: application/json" \
  -d '{"lines": []}'
# → { "error": "Validation failed", "details": [{ "field": "lines", "message": "At least one line required" }] }

POST /api/print/text

Print a multi-line text label.

Field Type Required Default Description
lines string[] Yes 1–20 lines of text
copies integer No 1 Number of copies (1–10)

POST /api/print/barcode

Print a standalone barcode label.

Field Type Required Default Description
data string Yes Barcode data to encode
type string No CODE128 Barcode type (see supported types below)
text string No Optional text printed below the barcode
height integer No 100 Barcode height in dots (10–1000)

POST /api/print/qr

Print a QR code label.

Field Type Required Default Description
data string Yes Data to encode in QR code
text string No Optional text printed below the QR code
magnification integer No 5 QR module size (1–10)

POST /api/print/zpl

Print raw ZPL commands. Accepts either text/plain body (raw ZPL string) or JSON:

Field Type Required Default Description
zpl string Yes Raw ZPL commands

POST /api/print/label

Print a composed label from element definitions. Uses the configured label size from settings for print width/height.

Field Type Required Default Description
elements array Yes Array of label elements (min 1)
copies integer No 1 Number of copies (1–10)

Each element has a type discriminator. Supported element types:

Element: text

Field Type Required Default Description
type "text" Yes Element type
content string Yes Text to print
options.x integer Yes X position in dots
options.y integer Yes Y position in dots
options.font string No "0" Zebra font ID
options.height integer No 24 Font height in dots
options.width integer No height × ratio Font width in dots (auto-derived from height if not specified)
options.ratio number No 0.8 Width-to-height ratio (0.1–3.0). Used to derive width from height or height from width when the other is not specified
options.rotation string No "N" Rotation: N (normal), R (90°), I (180°), B (270°)
options.reverse boolean No false Reverse print (white on black)

Element: barcode

Field Type Required Default Description
type "barcode" Yes Element type
content string Yes Data to encode
options.x integer Yes X position in dots
options.y integer Yes Y position in dots
options.type string Yes Barcode type (see table below)
options.height integer No 50 Barcode height in dots
options.narrowBarWidth integer No 2 Narrow bar width (1–10)
options.wideBarRatio number No 2.0 Wide-to-narrow ratio (2.0–3.0)
options.humanReadable boolean No true Print human-readable text below barcode
options.humanReadablePosition string No "Y" Y = below, N = hidden
options.rotation string No "N" Rotation: N, R, I, B

Element: qrcode

Field Type Required Default Description
type "qrcode" Yes Element type
content string Yes Data to encode
options.x integer Yes X position in dots
options.y integer Yes Y position in dots
options.magnification integer No 5 QR module size (1–10)
options.errorCorrection string No "M" Error correction: L (7%), M (15%), Q (25%), H (30%)

Element: raw

Field Type Required Default Description
type "raw" Yes Element type
zpl string Yes Raw ZPL commands to inject

Part Label Recipe

The web dashboard's "Quick Print Part Label" uses the /api/print/label endpoint with this layout (2×1" label at 203 DPI):

┌──────────────────────────────────┐
│  ┌─────┐  Part Name             │
│  │ QR  │  Part Number           │
│  │Code │  Rev X | Vendor        │
│  └─────┘                        │
└──────────────────────────────────┘

Barcode convention: The QR code content is {partNumber}-{rev}-{vendor}, which uniquely identifies the exact part variant. For example: 135853-002-A-NRG.

Multi-copy printing: To print one label per physical part, include a raw element with ^PQ{n} where n is the quantity. This tells the printer firmware to repeat the label without re-sending data.

Full example (3 copies):

curl -X POST http://localhost:3420/api/print/label \
  -H "Content-Type: application/json" \
  -d '{
    "elements": [
      {"type": "qrcode", "content": "135853-002-A-NRG", "options": {"x": 40, "y": 50, "magnification": 4}},
      {"type": "text", "content": "FTS Lens Mount", "options": {"x": 160, "y": 50, "height": 35, "width": 28}},
      {"type": "text", "content": "135853-002", "options": {"x": 160, "y": 95, "height": 30, "width": 28}},
      {"type": "text", "content": "Rev A | NRG", "options": {"x": 160, "y": 135, "height": 25, "width": 20}},
      {"type": "raw", "zpl": "^PQ3"}
    ]
  }'

Note: Use only ASCII characters in text content. Zebra printers do not support UTF-8 — multi-byte characters (em dashes, middle dots, accented letters) will render as garbled output.

POST /api/print/serial

Multi-copy print with auto-incrementing serial numbers. Use {serial} as placeholder in lines.

Field Type Required Default Description
lines string[] Yes 1–20 lines (use {serial} placeholder)
copies integer Yes Number of copies (1–500)
serialStart integer No 1 Starting serial number
serialFormat string No "###" Padding format: #, ##, ###, ####, #####

Supported Barcode Types

Type Description
CODE128 High-density alphanumeric (most common)
CODE39 Alphanumeric + symbols
CODE93 Compact alphanumeric
EAN8 8-digit retail (Europe)
EAN13 13-digit retail (Europe)
UPCA 12-digit retail (North America)
UPCE Compressed UPC
CODABAR Numeric with start/stop characters
PDF417 2D stacked barcode
QRCODE 2D QR code (use qrcode element type instead)
DATAMATRIX 2D Data Matrix

Coordinate System

All positions (x, y) are in dots at 203 DPI. The origin (0,0) is the top-left corner of the label.

Conversion Formula
Inches → dots inches × 203
mm → dots mm ÷ 25.4 × 203

For a 2×1" label: max X = 406, max Y = 203.
For a 4×6" label: max X = 812, max Y = 1218.

Label Sizes

The API tracks the current label size and recently used sizes. Standard sizes are pre-loaded:

Name Size (inches) Dots (203 DPI)
2×1" (small) 2 × 1 406 × 203
3×1" (narrow) 3 × 1 609 × 203
3×2" (standard) 3 × 2 609 × 406
3×5" (large) 3 × 5 609 × 1015
4×6" (shipping) 4 × 6 812 × 1218

Set a custom size with PUT /api/label-size. It'll be saved to the recent list for quick hot-swapping from the web UI.

Use as a Library

import { Printer, ZPLBuilder, PrintQueue } from 'zebra-label-printer';

// Auto-connect to any Zebra printer
const printer = await Printer.auto();

// Build a label
const zpl = new ZPLBuilder()
  .text('Hello World!', { x: 50, y: 50, height: 40, font: 'D' })
  .barcode('ABC-12345', { x: 50, y: 120, type: 'CODE128', height: 80 })
  .qrcode('https://example.com', { x: 50, y: 250, magnification: 5 })
  .build();

// Print with queue (persists to SQLite, auto-retries)
const queue = new PrintQueue(printer);
queue.start();
const result = await queue.submit('text', { lines: ['Hello'] }, () => zpl);

Network Setup

  1. Start the server on the machine connected to the printer:
    npx tsx src/server/index.ts
  2. Any device on the same network can print:
    curl -X POST http://printer-host:3420/api/print/text \
      -H "Content-Type: application/json" \
      -d '{"lines": ["Hello from my laptop!"]}'
  3. For PM2 (auto-start on boot):
    pm2 start npx --name zebra-label -- tsx src/server/index.ts
    pm2 save

Environment Variables

Variable Default Description
ZEBRA_PRINTER auto-detect CUPS printer name
ZEBRA_API_KEY none API key for Bearer auth
PORT 3420 Server port
ZEBRA_DB_PATH ./data/zebra-label-printer.db SQLite database path
NUXT_PUBLIC_API_BASE http://localhost:3420 API URL for web UI

Project Structure

src/              → TypeScript library + API server
  server/         → Modular HTTP server
  db/             → SQLite persistence (jobs, logs, settings)
  queue.ts        → Persistent job queue with background processor
web/              → Nuxt 4 web dashboard
Dockerfile        → Docker image
docker-compose.yml→ Docker orchestration
install.sh        → One-command install script
AI-MAP.md         → Agent quick-reference

Printer Setup

The GK420d should be auto-detected by CUPS when plugged in via USB. On this NUC it appears as ZTC-GK420d. If CUPS doesn't pick it up:

# Check USB detection
lsusb | grep -i zebra  # Should show "Zebra Technologies GK420d"

# Configure CUPS
sudo lpadmin -p ZTC-GK420d -E -v "usb://Zebra%20Technologies/ZTC%20GK420d?serial=YOURSERIAL" -m raw

Labels used: thermal direct labels (no ink needed). Default size: 3" × 5".

About

TypeScript library for Zebra GK420d label printers (and compatible ZPL printers). Print text, barcodes, QR codes, and custom labels — programmatically, via CLI, or through a REST webhook.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors