CLI tool
Klemen1337/node-thermal-printer avatar
Klemen1337/node-thermal-printer

node-thermal-printer: driving receipt printers from Node.js

Node.js module for Epson, Star, Tanca, Drauma, Custom and Brother thermal printers command line printing.

916 stars258 forksJavaScriptMIT

At a glance

What is it?
node-thermal-printer is a Node.js module for Epson, Star, Tanca, Daruma, Brother and custom thermal printers, with a printer object that exposes everything from alignment and table layout to QR codes, Code 128 barcodes and PNG images over TCP, a named system printer or a local port. It is a small library with four dependencies and a TypeScript declaration file.
Who is it for?
Adopt node-thermal-printer when you need to print receipts, labels or tickets from a Node.js service and you would rather not write escape sequences by hand for each printer family. Do not adopt it if your printing happens entirely in the browser, since the network interface cannot work there, and treat the browser case as unsupported rather than fixable, since it needs a bundler workaround.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 60 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One constructor, then a long list of verbs

node-thermal-printer is a Node.js module for Epson, Star, Tanca, Daruma, Brother and custom thermal printers, for command line printing. The API is a single object with a constructor and roughly forty methods, and reading the method list tells you exactly what the library believes a receipt printer should be able to do.

js
const { ThermalPrinter, PrinterTypes, CharacterSet, BreakLine } = require('node-thermal-printer');

const printer = new ThermalPrinter({
  type: PrinterTypes.STAR,
  width: 48,
  interface: 'tcp://xxx.xxx.xxx.xxx',
  characterSet: CharacterSet.PC852_LATIN2,
  removeSpecialCharacters: false,
  lineCharacter: "=",
  breakLine: BreakLine.WORD,
  options:{
    timeout: 5000
  }
});

The constructor takes the printer type, the number of characters in a line, the interface, the character set, whether special characters are stripped, the character used to draw lines, how lines are broken, and a connection timeout for network printers.

The rest of the surface covers text styling, alignment, spacing, tables, cuts, the cash drawer, the buzzer, images, barcodes and buffers. It is an unusually complete vocabulary for a small library, and it is the kind of completeness that comes from the library having been used against real printers rather than designed on paper.

The package is CommonJS, the entry point is node-thermal-printer.js, the implementation lives in the lib directory, and there is a node-thermal-printer.d.ts file at the root, so TypeScript users get declarations without the package being written in TypeScript.

Printer families, drivers and character sets

The printer type is chosen at construction and can be changed afterwards with setPrinterDriver, which takes an object and is described as default set on init. The type comment in the example names 'star' and 'epson' as the values, and the package description and keywords cover Epson, Star, Tranca, Daruma, Brother and Custom, so the module carries drivers for several families and a generic path for anything else.

Character sets are handled explicitly. The constructor takes a characterSet from the CharacterSet enum, with PC852_LATIN2 in the example, and there is a setCharacterSet method for changing it later, described as default set on init.

That the character set is a first-class option rather than an afterthought is the detail that matters for anyone printing outside ASCII. Receipt printers for different markets ship with different code pages, and getting this wrong produces a receipt full of accented characters and box drawing symbols. The dependency on iconv-lite is the reason this is possible, and it is one of only four runtime dependencies the package declares, the others being pngjs for images, unorm for Unicode normalisation and write-file-queue for serialising writes to the printer.

Two other behaviours are worth noting. removeSpecialCharacters defaults to false, and when it is enabled the characters that the target printer cannot render are stripped rather than emitted. And the breakLine option chooses whether text breaks after a word or after a character, with a NONE value that disables breaking, which is what you want when you are laying out a table yourself.

Interfaces: TCP, a named system printer, or a local port

The interface option is the part with the most deployment consequences, and the README tabulates it.

A TCP address with a port, in the form tcp://192.168.0.99:9100, is a network printer. The value printer:auto selects the raw system printer automatically, through either the Printer module or the Electron printer module, and printer:My Printer Name selects a system printer by name the same way. There is also a local port or file option, and the examples show a Windows path.

bash
node examples/example.js tcp://xxx.xxx.xxx.xxx
bash
node examples/example.js 'printer:My Printer'
bash
node examples/example.js '\\.\COM1'

Three things follow from that table.

First, the printer:auto and printer by name options depend on a separate module that this package does not list as a dependency, so choosing them means adding that dependency yourself. Second, the module ships a TypeScript declaration for its own API but not for that integration, so the types for those interfaces are on you. Third, the network interface is the only one that works on a headless server without any printing subsystem, which is why it is the one used in the main example.

The connection timeout option applies only to network printers and defaults to 3000 milliseconds, which is short enough to fail fast on a printer that is powered off and long enough for a normal LAN.

Buffered commands or raw bytes, and the printer's own buffer

There are two ways to send data, and the difference matters when you are integrating with a device that has its own expectations.

The execute method runs all the queued commands and returns success or throws an error. The raw method prints instantly from a buffer you supply.

js
const isConnected = await printer.isPrinterConnected();
const execute = await printer.execute();
const raw = await printer.raw(Buffer.from("Hello world"));

The buffered style is what the rest of the API uses. The print and println methods append text to an internal buffer rather than sending it, print appending and println appending with a new line. The buffer can then be inspected with getText, which returns the printer buffer string value, retrieved as a Buffer with getBuffer, replaced with setBuffer, and cleared with clear.

That is a useful level of control for a thermal printer, because it lets a caller compose a receipt, check what will be printed, adjust it, and then send it. It also means that a call to print without a later execute sends nothing, which is the sort of detail that costs an afternoon the first time.

The raw path is for the cases the command set does not cover, such as sending a payload the printer accepts in a form the library does not model. It bypasses the command layer entirely.

Tables, alignment and the layout commands that save the most time

The layout helpers are where a thermal printer library either earns its place or does not, because manual column alignment with a monospaced character grid is the single most tedious part of receipt formatting.

The table method prints a table equally, taking an array of strings, and tableCustom takes an array of objects with the text, align, width, cols and bold properties.

js
printer.table(["One", "Two", "Three"]);
printer.tableCustom([
  { text:"Left", align:"LEFT", width:0.5 },
  { text:"Center", align:"CENTER", width:0.25, bold:true },
  { text:"Right", align:"RIGHT", cols:8 }
]);

Widths are expressed as fractions, which is how you say this column takes half the line, and a cols value pins an exact column count instead. The leftRight method prints text at both ends of a line, which is the classic invoice total line.

Around those sit the individual controls. Alignment is set with alignCenter, alignLeft and alignRight. Text size is set with setTextSize taking a height and a width from 0 to 7, and there are presets for normal, double height, double width and quad area text. There are two font types, setTypeFontA and setTypeFontB, with A as the default, and line spacing is adjustable with setLineSpacing and resetLineSpacing.

Styling is limited to what the hardware supports, which is the right limit to impose: bold, invert for background and text colour inversion, underline with one dot thickness and underlineThick with two, and drawLine for a rule.

Barcodes, QR codes and PNG images

The printing commands cover the three cases that most receipt integrations need beyond text.

js
printer.code128("Code128");
printer.printQR("QR CODE");
await printer.printImage('./assets/olaii-logo-black.png');

Code 128 for a barcode, a QR code for a payment or verification link, and an image for a logo. The image call is asynchronous and takes a path, which is why it differs in shape from the other two.

Image support comes from the pngjs dependency, which means the supported format is PNG. That is worth knowing before you design a logo pipeline around the printer, because you would need to convert anything else yourself.

The paper handling commands sit alongside. cut cuts the paper, and the comment says to use it if the printer only supports one mode. partialCut cuts the paper leaving a small bridge in the middle, and the comment says to use it if the printer supports multiple cut modes. openCashDrawer kicks the cash drawer. beep makes the internal beeper or buzzer sound if there is one. And upsideDown rotates the content 180 degrees, which is what you need for a printer that is mounted upside down under a counter.

The examples directory makes the coverage visible, with separate files for barcodes, beeps, charcodes, charsets, connection status, folding, images from a buffer and from a file, multiple printers, status and the main example.

Linux needs build tools, browsers cannot use the network interface, and the metadata disagrees

Two installation notes are practical rather than decorative.

On Linux the module needs build essentials.

bash
sudo apt-get install build-essential

And in a browser the network interface will not work, because browsers do not allow opening TCP sockets. The workaround is to instruct the bundler to ignore node dependencies, and the README shows it for webpack.

bash
new webpack.IgnorePlugin({
  resourceRegExp: /^fs$|^net$/,
}),

That is a shim, not a supported mode. The library is built around sockets and file access, so the browser case only works for whatever subset your bundler can stub out.

Two metadata inconsistencies are worth flagging, because they are the sort of thing that decides a legal question. The repository licence field says MIT, while the package manifest declares ISC. And the README title says v4.6.1 while the published GitHub releases stop at v4.4.5 from 2025-03-25, so the tagged releases and the published package version are different numbers.

The manifest itself is otherwise informative. The package name is node-thermal-printer with version 4.6.1, it is published to npm, it declares only four runtime dependencies and pins them exactly, including iconv-lite at 0.6.3 and write-file-queue at 0.0.1. The development tooling is ESLint 9 with a flat configuration file at the root.

The last push was on 2026-08-24 and the default branch is master.

Editorial conclusion

Adopt node-thermal-printer when you need to print receipts, labels or tickets from a Node.js service and you would rather not write escape sequences by hand for each printer family. Do not adopt it if your printing happens entirely in the browser, since the network interface cannot work there, and treat the browser case as unsupported rather than fixable, since it needs a bundler workaround. Before the first install, note that Linux needs build-essential, check the licence yourself because the repository field says MIT while the package manifest says ISC, and read the interface table before writing code, because the printer:auto option pulls in an additional module that the package does not depend on by default.

Frequently asked questions

How do I install node-thermal-printer?

Run npm install node-thermal-printer. On Linux the module also needs build tools, which the README installs with sudo apt-get install build-essential.

Which printers does node-thermal-printer support?

The package description names Epson, Star, Tanca, Daruma, Brother and custom thermal printers. The printer type is chosen with PrinterTypes at construction time and can be changed later with setPrinterDriver.

How do I print over the network to an IP address?

Pass the address as the interface option, for example tcp://192.168.0.99:9100, and set the width to the number of characters in a line. The connection timeout option applies to network printers only and defaults to 3000 milliseconds.

Does node-thermal-printer work in a browser?

Not for the network interface, because browsers do not allow opening TCP sockets. The README shows how to instruct webpack to ignore node dependencies such as fs and net, which is a workaround rather than a supported mode.

Can node-thermal-printer print tables and images?

Yes. The table method prints an array of strings equally, tableCustom takes objects with text, align, width, cols and bold, and printImage prints a PNG file. Barcodes are printed with code128 and QR codes with printQR.

Official sources

  1. Issues
  2. Klemen1337/node-thermal-printer on GitHub
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/klemen1337-node-thermal-printer.svg)](https://hysenlabs.com/projects/klemen1337-node-thermal-printer)

Was this useful?