npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

escpos-builder-ts

v0.1.1

Published

Fluent ESC/POS command builder with multi-language text (Japanese, Chinese, Korean, European), QR code, barcode, and image support

Readme

escpos-builder-ts

日本語版 README はこちら / Japanese README

Work in progress — this package is under active development. The API may change without notice until v1.0.0.

A fluent, dependency-light ESC/POS command builder for thermal receipt printers, written in TypeScript.

  • Fluent API — chain text, formatting, QR codes, barcodes, images, and cuts
  • Multi-language — Japanese (Shift_JIS/CP932), Simplified/Traditional Chinese, Korean, and 20+ single-byte code pages (Western/Central European, Cyrillic, Greek, Turkish, Hebrew, Arabic, Vietnamese, …) with automatic code page / Kanji mode switching
  • QR codes — native GS ( k commands (model, module size, error correction)
  • Images — RGBA (canvas ImageData) or grayscale input, threshold or Floyd–Steinberg dithering, GS v 0 raster output
  • Barcodes — UPC-A/E, EAN-13/8, CODE39, ITF, CODABAR, CODE93, CODE128
  • Vendor commands.custom(bytes) escape hatch for model-specific commands
  • Zero runtime dependencies — pure TypeScript with bundled encoding tables; runs in Node.js, browsers (Web Bluetooth / WebUSB / WebSerial), Deno, and Bun
  • Fully typed, tested with Vitest, ESM + CJS dual package

Installation

npm install escpos-builder-ts

Works in Node.js ≥ 18 and modern browsers — no Buffer or other Node polyfills needed.

Quick start

import { EscPosBuilder } from 'escpos-builder-ts';

const data = new EscPosBuilder()
  .align('center')
  .size(2, 2)
  .textLine('RECEIPT')
  .size(1, 1)
  .align('left')
  .textLine('Apple        $1.00')
  .textLine('Banana       $0.50')
  .bold()
  .textLine('Total        $1.50')
  .bold(false)
  .align('center')
  .qrcode('https://example.com/receipt/123')
  .feed(3)
  .cut()
  .build(); // Uint8Array — send it to your printer (USB, TCP 9100, Bluetooth, ...)

The builder only produces bytes; transport is up to you. For example, over a network socket:

import { createConnection } from 'node:net';

const socket = createConnection(9100, '192.168.1.50', () => {
  socket.end(data);
});

Paper width and receipt layout

Paper width is a physical property of the printer — ESC/POS has no command to set it. Tell the builder how many half-width characters fit on one line (48 for 80 mm paper, 32 for 58 mm paper with Font A at 203 dpi — check your printer manual), and use the layout helpers:

const b = new EscPosBuilder({ encoding: 'japanese', width: 32 }); // 58 mm

b.rule()                       // --------------------------------
 .leftRight('りんご', '¥100')   // りんご                     ¥100
 .leftRight('バナナ', '¥50')
 .rule('=')
 .leftRight('合計', '¥150');

For multi-column item lines, table() takes column definitions (exactly one column may omit width to absorb the rest of the line; overlong cells are truncated):

b.table(
  [{}, { width: 4, align: 'right' }, { width: 8, align: 'right' }],
  [
    ['りんご', '2', '¥200'],
    ['バナナ', '10', '¥500'],
  ],
);

leftRight(), table(), and rule() measure display width correctly for mixed scripts: CJK characters count as 2 cells, halfwidth katakana as 1, and East Asian Ambiguous characters (box drawing , , , …) as 2 in CJK encodings and 1 elsewhere. The measurement function is exported as stringWidth(value, ambiguousAsWide?).

For images, keep width at or below the printable dot count (384 dots for 58 mm, 512–576 for 80 mm). To shrink the printable area itself, the left margin (GS L) and print area width (GS W) commands can be sent via .custom().

Multi-language text

Pass an encoding at construction or switch mid-stream with .encoding(). Code page (ESC t) and Kanji mode (FS & / FS .) commands are inserted automatically and only when the encoding actually changes.

import { EscPosBuilder, registerEncoding } from 'escpos-builder-ts';
import gbk from 'escpos-builder-ts/encodings/gbk';

registerEncoding(gbk); // Chinese / Korean are opt-in to keep bundles small

const data = new EscPosBuilder({ encoding: 'japanese' })
  .textLine('いらっしゃいませ')   // CP932 (Shift_JIS) in Kanji mode
  .encoding('cp437')
  .textLine('Thank you!')
  .encoding('gbk')
  .textLine('谢谢')
  .build();

Supported encodings

| Name | Language / region | Mechanism | | --- | --- | --- | | cp932 (shiftjis, japanese) | Japanese | Kanji mode, Shift_JIS | | gbk (gb2312)² | Simplified Chinese | Kanji mode | | big5² | Traditional Chinese | Kanji mode | | euckr (cp949, korean)² | Korean | Kanji mode | | cp437 (ascii) | USA / Standard Europe | ESC t 0 | | cp850, cp858, cp1252 (latin1), iso885915 | Western Europe | ESC t | | cp852, cp1250, iso88592 | Central Europe | ESC t | | cp866, cp1251 | Cyrillic | ESC t | | cp1253 | Greek | ESC t | | cp1254 | Turkish | ESC t | | cp1255 | Hebrew¹ | ESC t | | cp1256 | Arabic¹ | ESC t | | cp1257 | Baltic | ESC t | | cp1258 | Vietnamese | ESC t | | cp860, cp863, cp865 | Portuguese / Canadian French / Nordic | ESC t |

¹ Right-to-left shaping is not performed; the printer prints code points in the order given. ² Shipped as a subpath module to keep browser bundles small — import it (escpos-builder-ts/encodings/gbk, .../big5, .../euckr) and pass it to registerEncoding() once at startup. Japanese (cp932) and all single-byte code pages are built in. You can also register your own code pages or encoders with registerEncoding().

Note — multi-byte languages require a printer model with the corresponding character set installed (e.g. Japanese models for CP932). Code page numbers follow the Epson standard; for other vendors, check your printer manual and use .custom() if a different ESC t value is needed.

QR codes

builder.qrcode('https://example.com', {
  model: 2,              // 1 | 2 (default 2)
  size: 6,               // module size 1–16 (default 6)
  errorCorrection: 'M',  // 'L' | 'M' | 'Q' | 'H' (default 'M')
  encoding: 'utf8',      // payload encoding (default UTF-8)
});

Images

Accepts RGBA pixels (e.g. canvas ImageData) or 8-bit grayscale, and prints via GS v 0:

// In a browser / with node-canvas:
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
builder.image(imageData, { dither: 'floyd-steinberg' });

// Or grayscale bytes (0 = black, 255 = white):
builder.image({ data: grayPixels, width: 384, height: 200 }, { threshold: 128 });

To print PNG/JPEG files in Node.js, decode them first with a library such as sharp or jimp and pass the raw pixels.

Barcodes

builder.barcode('4901234567894', 'EAN13', {
  height: 80,           // dots, 1–255
  width: 2,             // module width, 2–6
  hriPosition: 'below', // 'none' | 'above' | 'below' | 'both'
  hriFont: 'a',
});

Vendor-specific commands

Every printer family has extensions beyond standard ESC/POS. Use .custom() (alias .raw()) to insert raw bytes anywhere in the chain:

builder
  .textLine('Hello')
  .custom([0x1b, 0x70, 0x00, 0x19, 0xfa]) // e.g. drawer kick
  .cut();

For reusable model-specific commands, extend the builder:

import { EscPosBuilder } from 'escpos-builder-ts';

class MyPrinterBuilder extends EscPosBuilder {
  buzzer(times = 1): this {
    return this.custom([0x1b, 0x42, times, 0x02]);
  }
}

new MyPrinterBuilder().textLine('Order ready').buzzer(3).cut().build();

API reference

| Method | ESC/POS | Description | | --- | --- | --- | | text(s) / textLine(s) | — | Print text (in the current encoding) | | rule(char?) | — | Horizontal rule spanning the line width | | leftRight(left, right, pad?) | — | Left- and right-aligned text on one line | | table(columns, rows) | — | Fixed-width columns with per-column alignment | | encoding(name) | ESC t / FS & | Switch text encoding | | newline(n?) / tab() / feed(n?) | LF / HT / ESC d | Whitespace and feeding | | bold(on?) | ESC E | Emphasized mode | | underline(mode?) | ESC - | Underline (off / 1-dot / 2-dot) | | invert(on?) | GS B | White/black reverse | | upsideDown(on?) | ESC { | Upside-down printing | | font('a'\|'b'\|'c') | ESC M | Character font | | align('left'\|'center'\|'right') | ESC a | Justification | | size(w, h?) | GS ! | Character magnification (1–8×) | | lineSpacing(dots?) | ESC 3 / ESC 2 | Line spacing | | qrcode(data, opts?) | GS ( k | QR code | | barcode(data, type, opts?) | GS k | Barcode | | image(src, opts?) | GS v 0 | Raster image | | cut(type?, feed?) | GS V | Full / partial cut | | cashDrawer(pin?, on?, off?) | ESC p | Drawer kick-out pulse | | init() | ESC @ | Initialize printer | | custom(bytes) / raw(bytes) | — | Raw bytes | | build() | — | Get the result as Uint8Array | | clear() | — | Discard accumulated commands |

Browser usage

build() returns a plain Uint8Array, so sending it from a browser is just a matter of picking a transport:

// Web Bluetooth
await characteristic.writeValueWithoutResponse(data);

// WebUSB
await device.transferOut(endpointNumber, data);

There is nothing to polyfill — the encoder is pure JavaScript and ships its own conversion tables.

Development

npm install
npm test                 # vitest
npm run typecheck        # tsc --noEmit
npm run build            # tsup → dist/ (ESM + CJS + d.ts)
npm run generate:tables  # regenerate src/tables/ from iconv-lite (devDependency)

Contributing

Issues and pull requests are welcome! See CONTRIBUTING.md.

Trademarks

QR Code is a registered trademark of DENSO WAVE INCORPORATED in Japan and other countries. EPSON and ESC/POS are registered trademarks of Seiko Epson Corporation. This project is not affiliated with or endorsed by these companies.

License

MIT © INIAD組み込み研究会 (INIAD Embedded)

The character-encoding tables in src/tables/ are generated from mapping data in iconv-lite (MIT License, Copyright (c) 2011 Alexander Shtuchkin).