thermal-printer-mock
v1.0.2
Published
Mock ESC/POS thermal printer with a live web preview — listens on TCP and shows every print job in the browser.
Maintainers
Readme
Thermal Printer Mock
A mock ESC/POS thermal printer for your laptop. Listens on a TCP port like a real network thermal printer, parses the bytes, and renders every print job live in a browser — so you can iterate on receipt layouts without owning a printer.
Why
Building POS / kitchen / e-commerce software that prints receipts is annoying:
- You don't always have a thermal printer at your desk.
- A real printer wastes paper on every layout tweak.
- Driver bugs are easier to spot when you can see the actual bytes being sent.
This tool stands in for an Epson / Star / Bixolon-style network printer
(tcp://host:9100 family). Point your application at it, and every job shows
up in the UI as a rendered receipt plus the raw text, hex dump, and parsed
ESC/POS tokens.
Quick start
Run instantly with npx
npx thermal-printer-mockThen open http://localhost:4000. The mock is listening on TCP :9104.
Or clone the repo
git clone https://github.com/<your-username>/thermal-printer-mock.git
cd thermal-printer-mock
npm install
npm startYou should see:
[web] UI ready at http://localhost:4000
[printer] mock thermal printer listening on tcp://0.0.0.0:9104Stop with Ctrl+C.
Point your application at it
Configure your app's printer driver to use a network/TCP printer:
| Setting | Value |
| ------- | ----------------- |
| Host | localhost |
| Port | 9104 |
Anything your app sends will appear as a new job in the UI within a few hundred ms of the connection closing or going idle.
Send a test print
A pre-built sample receipt is included:
npm run sample
# or against a custom host/port:
node scripts/sample-receipt.js 127.0.0.1 9104Quick smoke test using nc:
printf 'Hello mock printer!\n\n\n' | nc localhost 9104Configuration
Override defaults with environment variables:
PRINTER_PORT=9100 WEB_PORT=8080 npx thermal-printer-mock
HOST=127.0.0.1 npx thermal-printer-mock # bind only to loopback| Variable | Default | What it does |
| -------------- | --------- | ----------------------------------------- |
| PRINTER_PORT | 9104 | TCP port the mock printer listens on |
| WEB_PORT | 4000 | HTTP port for the live UI |
| HOST | 0.0.0.0 | Interface the printer socket binds to |
Web UI features
- Live receipt preview — rendered at 80 mm thermal-paper width
- Real-time updates via WebSocket as jobs arrive
- Four views per job — Receipt · Text · Hex dump · Parsed tokens
- Toolbar toggles — 32-column ruler, control codes, follow-latest
- Search & time-grouped job list (Today / Yesterday / Older)
- Stats footer — total jobs, total bytes, last activity
- Light / dark theme (persisted)
- Print or save as PDF the rendered receipt
Keyboard shortcuts
| Key | Action |
| ----------- | ---------------------------- |
| / | Focus search |
| j / ↓ | Next job |
| k / ↑ | Previous job |
| t | Toggle theme |
| ⌘K / ^K | Clear all jobs |
| ⌘P / ^P | Print current receipt |
| Esc | Clear search field |
Supported ESC/POS commands
Text styling, alignment, font size, double-width / double-height, underline,
invert, line feed, paper cut, 1D barcodes, 2D / QR placeholder, raster &
bit images (placeholder), cash drawer pulse, init, code page select, and
more. Unknown commands are recorded as unknownEsc / unknownGs tokens and
shown inline so you can spot what your driver sends.
The wire bytes are always preserved — open the Hex tab on any job for the exact stream.
HTTP API
| Method | Path | Description |
| -------- | ---------------- | -------------------------------- |
| GET | /api/history | All buffered jobs as JSON |
| DELETE | /api/history | Clear the job buffer |
A WebSocket on the same port pushes init, job, and cleared events.
Troubleshooting
Port already in use. Check what's holding it:
lsof -nP -iTCP:9104 -sTCP:LISTENKill it, or pick another port via PRINTER_PORT=....
Nothing shows up in the UI. The mock auto-finalizes a job ~400 ms after the last byte, or when the client closes the connection. If your driver keeps the socket open and idle without sending more data, nothing has been "completed" yet — close the connection from the client side or send a cut command.
Shadowed bind on macOS. If another process is bound to 127.0.0.1:PORT
and this server is bound to *:PORT, local connections will hit the other
process first. Either kill the conflicting process or change ports.
Project layout
thermal-printer-mock/
├── server.js # TCP listener + Express/WebSocket UI server
├── lib/parser.js # ESC/POS stream parser → tokens
├── public/ # Static UI (HTML / CSS / JS)
└── scripts/
└── sample-receipt.js # Demo receipt generatorContributing
PRs welcome — especially for ESC/POS commands not yet covered, real QR-code decoding, or driver-specific quirks. Open an issue first if it's a bigger change.
