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

epos-printer-sdk

v0.6.0

Published

Modern TypeScript library port of Epson's ePOS SDK, transcription from vendor's IIFE bundle. Ships EposHttpPrinter, a lightweight, socket-free, fully async/await client for the ePOS-Print HTTP web service (built for the TM-T88V), plus the full callback-ba

Readme

epos-printer-sdk

npm CI license types bundle

English · Español Try it in the browser, the demo runs against a simulated printer, so no hardware is needed.

Print to Epson TM receipt printers from JavaScript, over plain HTTP, with async/await, full TypeScript types, and no dependencies.

A ground-up TypeScript reimplementation of Epson's ePOS-Print protocol, built by reverse-engineering their official SDK and verified against the official Epson XML manuals and real TM-T88V hardware.

import { EposHttpPrinter } from 'epos-printer-sdk/http';

const printer = new EposHttpPrinter('192.168.1.100');

await printer
  .addText('Hello, World!\n')
  .addCut('feed')
  .send();

Why this one

Epson ships the ePOS SDK as a minified, undocumented IIFE meant to be dropped into a <script> tag: no modules, no types, no tree-shaking, and an entirely callback-driven API. This package is a modern replacement.

  • Zero dependencies, ~7 KB gzipped. npm install epos-printer-sdk pulls in nothing at all: no lodash, no dayjs, no bundled crypto, no Socket.IO, and npm audit reports 0 vulnerabilities.
  • Framework-agnostic, and it runs on the server. Plain fetch, so it works in React, Vue, Svelte, vanilla, and in Node 18+ (API routes, SSR, scripts, queue workers). Not a React-only wrapper.
  • Promise-native. send() resolves with the printer's actual response instead of making you wire up onreceive/onerror first.
  • Complete protocol coverage. Text and formatting, 1D barcodes, 2D symbols (QR/PDF417/DataMatrix/Aztec/MaxiCode), canvas images, page-mode labels, status decoding, print-job tracking, cash-drawer kick.
  • Typed against the real spec. Barcode/symbol/level unions were checked against the validation regexes in Epson's own bundle and the official XML manuals, not guessed.
  • Safe under concurrency. Requests to the same printer are serialized automatically, because the hardware processes them one at a time anyway. Ten simultaneous jobs with a 2s timeout against a real TM-T88V: 4/10 succeed without this, 10/10 with it.
  • Verified, not just written. 106 unit tests for the library and 18 for the demo, plus opt-in integration tests that run against a physical printer.

Install

pnpm add epos-printer-sdk
yarn add epos-printer-sdk
npm install epos-printer-sdk

Requires Node 18+ (for native fetch) or any modern browser.

Quick start

import { EposHttpPrinter } from 'epos-printer-sdk/http';

// Port defaults to 443 (https). Pass { port: 80 } for plain http.
const printer = new EposHttpPrinter('192.168.1.100');

// Optional: verify the printer answers before sending a job.
await printer.connect(); // throws a PrintServiceError saying why, if it can't

const result = await printer
  .addTextAlign('center')
  .addTextSize(2, 2)
  .addText('MY STORE\n')
  .addTextSize(1, 1)
  .addText('Thanks for your visit!\n')
  .addFeedLine(2)
  .addCut('feed')
  .send();

if (!result.success) {
  console.error('Print failed:', result.code);
}

send() resolves with:

{ success: boolean, code: string, status: number, battery: number, printjobid: string }

The builder buffer is consumed on every send(), so the same instance can be reused for the next job without re-printing the previous one.

Recipes

Receipt with a total

await printer
  .addTextAlign('center')
  .addTextStyle(false, false, true)   // bold
  .addText('MY STORE\n')
  .addTextStyle(false, false, false)
  .addTextAlign('left')
  .addText('Coffee            $ 3.50\n')
  .addText('Sandwich          $ 6.00\n')
  .addText('------------------------\n')
  .addTextStyle(false, false, true)
  .addText('TOTAL             $ 9.50\n')
  .addFeedLine(2)
  .addCut('feed')
  .send();

Barcode

await printer
  .addTextAlign('center')
  .addBarcode('0123456789', 'code128', 'below')
  .addFeedLine(1)
  .addCut('feed')
  .send();

Supported types: upc_a, upc_e, ean13, jan13, ean8, jan8, code39, itf, codabar, code93, code128, code128_auto, gs1_128, and the four gs1_databar_* variants.

QR code / 2D symbols

await printer
  .addTextAlign('center')
  .addSymbol('https://example.com', 'qrcode_model_2', 'level_m', 4)
  .addFeedLine(1)
  .addCut('feed')
  .send();

Also supports PDF417, DataMatrix, Aztec, MaxiCode and stacked GS1 DataBar, see SymbolType.

Image from a canvas

const canvas = document.querySelector('canvas')!;

// Pass a printjobid to be able to track the job afterwards.
const jobId = `receipt-${Date.now()}`;
await printer.print(canvas, jobId);

// Large images keep printing after the request is accepted, poll to confirm.
const status = await printer.getPrintJobStatus(jobId);

Label (page mode)

Page mode positions content in a fixed-size area instead of the receipt's sequential flow:

await printer
  .addPageBegin()
  .addPageArea(0, 0, 380, 120)
  .addPageDirection('left_to_right')
  .addPagePosition(10, 30).addText('Product name')
  .addPagePosition(10, 60).addText('SKU-00042')
  .addPageRectangle(0, 0, 379, 119, 'thin')
  .addPageEnd()
  .send();

Printer status

import { EposHttpPrinter, decodePrinterStatus } from 'epos-printer-sdk/http';

const res = await printer.send();           // no content queued = status query
const status = decodePrinterStatus(res.status, res.battery);

// { online: true, coverOpen: false, paper: 'ok', drawerOpen: false, battery: 0, raw: 251658262 }
if (status.paper === 'near_end') {
  console.warn('Paper is running low');
}

Live status monitoring

printer.interval = 3000;
printer.onstatuschange = () => {
  console.log(decodePrinterStatus(printer.status, printer.battery));
};
printer.onpaperend = () => alert('Out of paper!');
printer.oncoveropen = () => alert('Cover is open');

printer.startMonitor();  // starts polling
printer.stopMonitor();   // stops it

open() / close() are the same thing under the vendor's other name. Both live on EposHttpPrinter and on the Printer that ePOSDevice.createDevice() returns: the poll is one implementation, shared.

Cash drawer

await printer.addPulse('drawer_1', 'pulse_100').send();

Handling failures

A print can fail for very different reasons, and they need different responses: retrying a job that failed because the paper ran out just wastes time, while not retrying a job that hit a busy printer loses a receipt. send() rejects only when the printer can't be reached; a printer that answers but refuses the job resolves with success: false and a code.

The printer can't be reached

connect() and a failed send() reject with a PrintServiceError, whose code says which kind of unreachable it was, so the branch is a switch and not a string match. message is that same reason in Spanish, ready to show; status and responseText keep the raw detail for a log.

| code | What happened | What it usually means | |---|---|---| | TIMEOUT | Nothing answered before timeout ran out | The printer is off or unplugged | | UNREACHABLE | The request never got out: name doesn't resolve, connection refused, TLS or CORS | Nothing on the network claims that address | | ERROR | Something answered, but not the ePOS service (non-2xx, or unparseable) | Wrong endpoint or deviceId | | ERROR_PARAMETER | The address isn't a requestable URL | Bad configuration, an empty host included |

import { EposHttpPrinter, PrintServiceError, PRINT_SERVICE_ERRORS } from 'epos-printer-sdk/http';

try {
  await printer.connect();
} catch (err) {
  if (!(err instanceof PrintServiceError)) throw err;
  switch (err.code) {
    case PRINT_SERVICE_ERRORS.TIMEOUT:     return askOperator('Is the printer on?');
    case PRINT_SERVICE_ERRORS.UNREACHABLE: return askOperator('Check the printer address.');
    default:                               return report(err.message);
  }
}

Three of those four are the values ePOSDevice.connect() resolves with (CONNECT_RESULTS). UNREACHABLE is the extra one: the socket probe folds it into TIMEOUT, while over HTTP the two are worth telling apart. A printer that is merely off costs the whole timeout; an address that names nothing fails in milliseconds.

The printer answers, and refuses the job

| code | Meaning | What to do | |---|---|---| | ERROR_DEVICE_BUSY | Another client is printing | Retry with backoff, expected with several clients | | TooManyRequests, EX_SPOOLER | Queue full | Retry, longer backoff | | JobSpooling, Printing | Still working on it | Poll getPrintJobStatus() | | EPTR_REC_EMPTY | Out of paper | Ask the operator; don't retry blindly | | EPTR_COVER_OPEN | Cover open | Ask the operator | | EPTR_CUTTER, EPTR_MECHANICAL | Jam / mechanical fault | Operator, then recover() | | EPTR_AUTOMATICAL | Recoverable fault | Call recover(), then retry | | EPTR_UNRECOVERABLE | Needs a power cycle | Operator | | SchemaError | Malformed XML | Bug in your call, don't retry | | DeviceNotFound | Wrong deviceId | Fix configuration | | RequestEntityTooLarge | Job too big | Split it up |

A minimal retry helper for the transient cases:

const TRANSIENT = ['ERROR_DEVICE_BUSY', 'TooManyRequests', 'EX_SPOOLER'];

async function printWithRetry(job: () => Promise<PrintServiceResponse>, attempts = 3) {
  for (let i = 1; i <= attempts; i++) {
    try {
      const res = await job();
      if (res.success || !TRANSIENT.includes(res.code)) return res;
    } catch (err) {
      if (i === attempts) throw err;   // unreachable printer, also transient
    }
    await new Promise((r) => setTimeout(r, 500 * 2 ** (i - 1)));
  }
  throw new Error('Printer unavailable after retries');
}

The React example app implements this end to end, with a panel that classifies every response code and offers the matching action.

Testing without a printer

epos-printer-sdk/simulator is a simulated printer you can hand to EposHttpPrinter. It speaks the real protocol, so code written against it behaves the same against hardware, and it models paper, cover and drawer state so failure paths can be exercised on purpose.

import { EposHttpPrinter } from 'epos-printer-sdk/http';
import { createSimulator } from 'epos-printer-sdk/simulator';

const sim = createSimulator({ initialState: { paper: 2 } });
const printer = new EposHttpPrinter('demo', { fetch: sim.fetch });

await printer.addText('hello
').addCut('feed').send();
sim.jobs[0].text;            // 'hello
'

sim.state.coverOpen = true;  // the next print fails with EPTR_COVER_OPEN

It is a separate entry point, so none of it reaches consumers who don't import it. The live demo runs entirely on it, which is why it needs no printer on the network.

API

new EposHttpPrinter(host, options?)

| Option | Type | Default | Description | |---|---|---|---| | port | number | 443 | 80/8008 switch the scheme to http | | deviceId | string | 'local_printer' | ePOS device id | | timeout | number | 10000 | Request timeout in ms |

| Method | Returns | Description | |---|---|---| | connect() | Promise<PrintServiceResponse> | Health check; rejects with a PrintServiceError whose code says why | | send() | Promise<PrintServiceResponse> | Sends what was built (or queries status) | | print(canvas, printjobid?) | Promise<PrintServiceResponse> | Renders and prints a canvas | | getPrintJobStatus(id) | Promise<PrintServiceResponse> | Status of a previous job | | recover() / reset() | Promise<PrintServiceResponse> | Clear a recoverable error | | startMonitor() / stopMonitor() | boolean | Start/stop status polling | | open() / close() | void | The same pair, under the vendor's other name |

Builder methods (all chainable):

  • Text: addText, addTextAlign, addTextSize, addTextDouble, addTextStyle, addTextFont, addTextLang, addTextLineSpace, addTextRotate, addTextSmooth, addTextPosition, addTextVPosition
  • Layout: addFeed, addFeedLine, addFeedUnit, addFeedPosition, addLayout, addHLine, addVLineBegin, addVLineEnd, addRotateBegin, addRotateEnd
  • Graphics: addBarcode, addSymbol, addImage, addLogo
  • Page mode: addPageBegin, addPageArea, addPageDirection, addPagePosition, addPageLine, addPageRectangle, addPageEnd
  • Device: addCut, addPulse, addSound, addRecovery, addReset, addCommand

Events (for push-based state, still callback-style by nature): onstatuschange, onbatterystatuschange, ononline, onoffline, onpoweroff, oncoveropen, oncoverok, onpaperend, onpapernearend, onpaperok, ondraweropen, ondrawerclosed, onbatterylow, onbatteryok, onreceive, onerror.

Constants

Every constant is exported from the package, and importing none of them costs nothing:

import { CUT_NO_FEED, ALIGN_CENTER, ASB_COVER_OPEN, PRINT_SERVICE_ERRORS } from 'epos-printer-sdk/http';
import { TYPES, DEVICE_TYPE_PRINTER, CONNECT_RESULTS } from 'epos-printer-sdk';

Classes you construct yourself (EposHttpPrinter, ePOSDevice) also carry them as instance constants, because that is how Epson's own documentation calls them: pos.addCut(pos.CUT_FEED), dev.createDevice(id, dev.DEVICE_TYPE_PRINTER). The rule is that both forms exist under the same name and resolve to the same value, and a test enforces it. Devices you never construct by hand (CAT, CashChanger, handed to you by createDevice()) keep their constants on the instance only.

Bundle size

Two entry points, so HTTP-only consumers never pull in the socket transport:

| Import | Contents | Size (gzip) | |---|---|---| | epos-printer-sdk/http | EposHttpPrinter, decodePrinterStatus, types | 8.6 KB | | epos-printer-sdk | Everything, incl. ePOSDevice + device management | 18.5 KB eager, 32 KB more on demand (+16 KB socket.io-client, only if you install it, see below) |

The legacy [email protected] the ePOS-Device socket transport needs is an optional peer dependency: it is not installed by default, because it drags in transitive packages with known CVEs and most printers (including every plain TM-T88V) don't host that service anyway. Install it explicitly only if you need the socket transport:

pnpm add [email protected]

sideEffects: false plus a proper exports map, so Vite/webpack/Rollup/esbuild tree-shake it without extra configuration.

Using it with React

There is no React binding to install, the client is a plain object, so a small hook is all you need:

function usePrinter(host: string) {
  const printer = useMemo(() => new EposHttpPrinter(host), [host]);
  return printer;
}

Because the HTTP transport is stateless (every job is an independent request), there is no connection to keep alive, drop, or re-establish.

A complete example, connection UI, live status, barcodes, QR, labels, canvas printing with job tracking, error classification with recommended actions, and several printers at once, lives in examples/react-app.

Device management (ePOSDevice)

Beyond plain printing, ePOSDevice is the session and device-management layer: connection lifecycle, createDevice(), communication boxes. Despite the name it is not tied to the socket transport, it runs over either one and picks which. Pass { eposprint: true } to go straight to HTTP; otherwise it tries the socket and falls back to HTTP on its own.

Use it when you need devices beyond a plain printer (cash drawers, CAT terminals, DeviceTerminal). For printing alone, EposHttpPrinter is lighter and never loads any of this.

import { ePOSDevice } from 'epos-printer-sdk';

const epos = new ePOSDevice();
const result = await epos.connect('192.168.1.100', 8008);
if (result !== 'OK') throw new Error(result); // 'TIMEOUT' | 'ERROR' | 'ERROR_PARAMETER'

const printer = await epos.createDevice('local_printer', 'type_printer');
await printer.addText('Hi\n').addCut('feed').send();

connect() says why it failed: TIMEOUT when nothing answered (printer off, unplugged, wrong address), ERROR when something answered but not the ePOS service, and ERROR_PARAMETER when the address isn't a usable URL. Compare against CONNECT_RESULTS rather than string literals.

Note the port rule differs from EposHttpPrinter: here only 8008 selects plain HTTP, anything else (including 80) is treated as HTTPS. That is the vendor's own mapping, kept for parity.

Note that plain TM-T88V printers don't host the ePOS-Device service at all (only TM-i, TM-DT and TM-T88VI+ models do), on those, connect() transparently falls back to HTTP, which is what the official SDK does too.

Compatibility notes

  • HTTPS and certificates. Browsers block plain-HTTP requests from an HTTPS page, so production usually needs the printer reachable over HTTPS. Printers serve a self-signed certificate, which has to be accepted once per client machine, putting the printer behind a reverse proxy with a real certificate avoids this.
  • CORS. Epson's service.cgi responds with Access-Control-Allow-Origin: *, so browser calls work without a proxy.
  • Concurrency. The library serializes requests to the same printer automatically within one process (a browser tab, a Node process). That does not extend across separate clients: for those, handle ERROR_DEVICE_BUSY with retries as shown above, or funnel jobs through a server-side queue (the library runs on Node, so it's the same code).

Known limitations

  • Encrypted socket communication (crypto: true) has not been validated against real hardware.
  • type_display (ePOS-Display) devices are not probed.
  • 18 of the ~22 device subclasses in the original SDK (customer displays, keyboards, MSR readers, hybrid/slip printers, fiscal printers) are not ported, none apply to a TM-T88V.

Docs

  • Engineering notes, how the SDK was reverse-engineered, the bugs found in the original bundle, and what's verified vs. assumed.
  • Changelog
  • llms.txt, machine-readable summary of the API and its gotchas, for coding assistants. Also served at https://unpkg.com/epos-printer-sdk/llms.txt.
  • AGENTS.md, conventions for agents and humans contributing here.

Contributing

Issues and PRs welcome. The highest-value areas are broadening hardware coverage and test depth; the WebSocket/encryption path is intentionally on hold.

License

MIT © Guido Wagner. See LICENSE.

Not affiliated with, endorsed by, or supported by Seiko Epson Corporation. "ePOS", "TM-T88V" and related marks belong to their respective owners.