js package providing an abstraction over different thermal printer languages
npm install label-printer> :warning: label-printer is 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)
``bash`
npm install label-printer
The library exposes three main areas:
- Commands: import { commands } from "label-printer"import { labels } from "label-printer"
- Labels: import { printers } from "label-printer"
- Printers:
- Uses WebUSB to communicate with USB label printers.
- Typical entry point: printers.PrinterService.requestPrinter().
- Supports USB printing (where supported by the usb dependency).9100
- Supports network printing over TCP (raw printing, usually port ).~!I
- 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 ()
`ts
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]
`
`ts
import { printers } from "label-printer"
const printer = await printers.PrinterService.requestPrinter()
if(!printer) throw new Error("No printer selected")
`
`ts
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()
`
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):
`ts
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):
`ts
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):
`ts
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):
`ts
import { printers } from "label-printer"
const printer = await printers.PrinterService.connect({
usb: { vendorId: 0x1234, productId: 0x5678 }
})
if(!printer) throw new Error("No printer selected")
`
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.
The label layer provides a language-independent way to construct labels, which can then be rendered to commands for the chosen printer language.
`ts
import { labels } from "label-printer"
const label = new labels.Label(50, 25)
// label.add(...fields)
`
- TSPL
Fields are available from the package root export.
Create a text field at (x, y) in dots.
`ts
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:
- ...: bold (uses weight 700)...
- : italic...
- : underline
- ...: strike
Draw a line between two points (values in dots).
`ts
import { labels } from "label-printer"
label.add(new labels.Line({ x: 10, y: 10 }, { x: 300, y: 10 }, 3))
`
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.
`ts
import { labels } from "label-printer"
const img = await labels.Image.create("./logo.png", 10, 60, 200)
label.add(img)
`
Draw a barcode (TSPL-backed). Values are in dots.
`ts
import { labels } from "label-printer"
const barcode = new labels.BarCode("123456789", 20, 120, "CODE128", 80)
barcode.setHumanReadable("bottom")
barcode.setRotation(0)
label.add(barcode)
`
Draw a QR code.
`ts
import { labels } from "label-printer"
label.add(new labels.QRCode("https://example.com", 20, 220, 6))
`
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.
`ts
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.height are 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.
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.
- Label
- Construct labels and add fields
- Generate language-specific print/display commands via printer layer
- PrinterService
- getPrinters()requestPrinter()
- Browser: discovers accessible USB printers
- Node: discovers USB printers + network printers (Bonjour/mDNS, then subnet scan fallback)
- undefined
- Browser: prompts user for a USB device
- Node: selects first available USB device (may return )Printer
- print(label, sets, gap, copiesPerSet?, direction?, mirror?, gapOffset?)
- display(label, direction?, mirror?)
- close()
-
- 1 pt = 1/72 inch
- 1 dot = 1 / dpi
There are two ways to use fonts:
1. Use printer built-in fonts (by using name: "default")Label
2. Register and use custom fonts on a per- basis
#### Set a font on Text / Table
Fonts are configured using a FontOption:
`ts`
text.setFont({ name: "default", size: 10 })
// or
text.setFont({ name: "MyFont", size: 18, weight: 700, style: "normal" })
- size is specified in dots.weight
- defaults to 400.style
- defaults 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.
`ts
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: 700 variant....
- When using formatted text (), the library will request weight: 700.
- Make changes on feature branch.
- Run pnpm changeset on feature branch to create change.pnpm changeset version` on main to create an update with all the versions.
- Merge feature branch into main.
- Run
- PR is automatically created on push, merge it to trigger publish
- Note: If there are unpublished changes on main and the release fails, it will be run again on the next push to main, no need to start again from the first step