label-printer
v0.8.0
Published
js package providing an abstraction over different thermal printer languages
Readme
Label Printer
:warning:
label-printeris still under heavy development and is subject to frequent API changes
This package provides a TypeScript/JavaScript API to:
- Build labels in a printer-language-independent way
- Generate printer commands (currently TSPL focused)
- Find and talk to printers
- Browser: via WebUSB
- Node.js: via USB and automatic network discovery (TCP/9100)
Installation
npm install label-printerMain exports
The library exposes three main areas:
- Commands:
import { commands } from "label-printer" - Labels:
import { labels } from "label-printer" - Printers:
import { printers } from "label-printer"
Runtime support (Browser vs Node)
Browser
- Uses WebUSB to communicate with USB label printers.
- Typical entry point:
printers.PrinterService.requestPrinter().
Node.js
- Supports USB printing (where supported by the
usbdependency). - Supports network printing over TCP (raw printing, usually port
9100). - Supports automatic network discovery:
- First attempts Bonjour/mDNS discovery of printer-related services
- If Bonjour yields no candidates, falls back to a conservative private-subnet scan
- Every discovered candidate is verified by sending the TSPL identify command (
~!I)
Printer layer
Discover printers
import { printers } from "label-printer"
const printersList = await printers.PrinterService.getPrinters()
if(printersList.length === 0) {
throw new Error("No printers found")
}
const printer = printersList[0]Request a printer (browser-focused)
import { printers } from "label-printer"
const printer = await printers.PrinterService.requestPrinter()
if(!printer) throw new Error("No printer selected")Print or display a label
import { labels } from "label-printer"
const label = new labels.Label(50, 25)
// 1 label, 3mm gap
await printer.print(label, 1, 3)
// or
await printer.display(label)
await printer.close()Connect directly (bypass discovery)
If you already know how to reach your printer (network address or USB identifiers), you can create a printer instance directly.
Auto-detect language (recommended default):
import { printers } from "label-printer"
const printer = await printers.PrinterService.connect({ network: { host: "192.168.100.31" } })
if(!printer) throw new Error("Printer not found or not supported")Explicit TSPL (when you know it's a TSPL printer):
import { printers } from "label-printer"
const printer = await printers.PrinterService.connectTSPL({ network: { host: "192.168.100.31", port: 9100 } })
if(!printer) throw new Error("Not a TSPL printer")USB (Node.js - filter without a prompt):
import { printers } from "label-printer"
const printer = await printers.PrinterService.connect({
usb: { vendorId: 0x1234, productId: 0x5678, serialNumber: "ABC" }
})
if(!printer) throw new Error("Printer not found or not supported")USB (Browser - shows a picker; you can optionally filter by vendorId / productId):
import { printers } from "label-printer"
const printer = await printers.PrinterService.connect({
usb: { vendorId: 0x1234, productId: 0x5678 }
})
if(!printer) throw new Error("No printer selected")Device abstraction
Commands write to a transport-agnostic Device interface. This enables the same printer and label APIs to work over different transports.
- USB device implementation is internal to
USBUtils. - Network support uses a TCP implementation in Node.js.
Label layer
The label layer provides a language-independent way to construct labels, which can then be rendered to commands for the chosen printer language.
import { labels } from "label-printer"
const label = new labels.Label(50, 25)
// label.add(...fields)Supported languages
Fields
Fields are available from the package root export.
Text
Create a text field at (x, y) in dots.
import { labels } from "label-printer"
const label = new labels.Label(50, 25)
const text = new labels.Text("Hello", 20, 20, true)
text.setSingleLine(200)
label.add(text)Text wrapping/clipping:
text.setSingleLine(width?)text.setMultiLine(width, height?)
Formatted text (when formatted = true) supports basic tags:
<b>...</b>: bold (uses weight700)<i>...</i>: italic<u>...</u>: underline<s>...</s>: strike
Line
Draw a line between two points (values in dots).
import { labels } from "label-printer"
label.add(new labels.Line({ x: 10, y: 10 }, { x: 300, y: 10 }, 3))Image
Draw a black/white bitmap image. You can either provide a bitmap-like object directly, or use the async helper to load/convert an image.
import { labels } from "label-printer"
const img = await labels.Image.create("./logo.png", 10, 60, 200)
label.add(img)BarCode
Draw a barcode (TSPL-backed). Values are in dots.
import { labels } from "label-printer"
const barcode = new labels.BarCode("123456789", 20, 120, "CODE128", 80)
barcode.setHumanReadable("bottom")
barcode.setRotation(0)
label.add(barcode)QRCode
Draw a QR code.
import { labels } from "label-printer"
label.add(new labels.QRCode("https://example.com", 20, 220, 6))Table
The Table field draws a grid and places text into each cell. It uses the existing Text field for cell contents and the existing Line field for the grid lines.
import { labels } from "label-printer"
const label = new labels.Label(50, 25)
const table = new labels.Table(10, 10, [
["A1", "A2"],
["B1", "B2"],
], {
size: { width: 200, height: 100 },
columnWidths: [80, 120],
rowHeights: [40, 60],
lineThickness: 2,
cellPadding: 4,
formatted: false,
font: { name: "default", size: 10 },
})
label.add(table)Sizing rules:
- If
size.width/size.heightare set- Any unspecified row/column sizes share the remaining space equally.
- Cell text is constrained to the cell content box and will wrap to multiple lines. If a row height is fixed, the text may be clipped once it reaches the height limit.
- If table size is not set
- Unspecified row/column sizes are measured from their content.
Command layer
The command layer is the lowest level and represents printer-language-specific commands.
Most users will not need this directly. It is primarily used internally by Label to generate
print/display command sequences.
Public API summary
Label- Construct labels and add fields
- Generate language-specific print/display commands via printer layer
PrinterServicegetPrinters()- Browser: discovers accessible USB printers
- Node: discovers USB printers + network printers (Bonjour/mDNS, then subnet scan fallback)
requestPrinter()- Browser: prompts user for a USB device
- Node: selects first available USB device (may return
undefined)
Printerprint(label, sets, gap, copiesPerSet?, direction?, mirror?, gapOffset?)display(label, direction?, mirror?)getModelname()getStatus()writeRawString(text)close()
PrinterStatus- String union of possible status values returned by
printer.getStatus(). - Access via
printers.PrinterStatus.
- String union of possible status values returned by
Notes
Useful units
- 1 pt = 1/72 inch
- 1 dot = 1 / dpi
Fonts
There are two ways to use fonts:
- Use printer built-in fonts (by using
name: "default") - Register and use custom fonts on a per-
Labelbasis
Set a font on Text / Table
Fonts are configured using a FontOption:
text.setFont({ name: "default", size: 10 })
// or
text.setFont({ name: "MyFont", size: 18, weight: 700, style: "normal" })sizeis specified in dots.weightdefaults to400.styledefaults to"normal".
Register a custom font on a Label
Registering fonts enables better text measurement (for wrapping) and ensures the font is uploaded as part of the generated print/display command sequence.
import { labels } from "label-printer"
const label = new labels.Label(50, 25)
await label.registerFont({
name: "MyFont",
data: await (await fetch("/fonts/MyFont-Regular.ttf")).arrayBuffer(),
weight: 400,
style: "normal",
})
await label.registerFont({
name: "MyFont",
data: await (await fetch("/fonts/MyFont-Bold.ttf")).arrayBuffer(),
weight: 700,
style: "normal",
})Font notes:
- If a font is not working, make sure the extension is TTF.
- If you want bold text, register a
weight: 700variant. - When using formatted text (
<b>...</b>), the library will requestweight: 700.
Update package
- Make changes on feature branch.
- Run
pnpm changeseton feature branch to create change. - Merge feature branch into main.
- Changeset will create a PR, merge it to trigger publish
