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

react-sunmi-thermal-printer

v1.0.0

Published

Direct browser-to-printer printing for Sunmi thermal printers. React hooks and ESC/POS utilities for POS receipts, KOTs, invoices, and billing slips — no drivers, no middleware.

Readme

react-sunmi-thermal-printer

Direct browser-to-printer printing for Sunmi thermal printers — no drivers, no middleware, no desktop app.

A React library that solves one specific problem: sending print jobs directly from a web browser to a Sunmi thermal printer over your local network using HTTP.

Built for web-based POS systems, restaurant management platforms, and any browser app that needs to print receipts, KOTs, invoices, or billing slips on a Sunmi device.


The Problem This Solves

Most web-based POS systems struggle with printing because:

  • Browsers don't support raw socket connections to printers
  • Traditional print dialogs are slow and require user interaction
  • Third-party print servers add infrastructure complexity
  • Native drivers don't work inside a browser

Sunmi printers expose a local HTTP endpoint (http://<printer-ip>/cgi-bin/print.cgi) that accepts raw ESC/POS commands as a POST body. This package gives you React hooks and a command library to build and send those commands directly from your browser app — instant, silent, zero-click printing.


Use Cases

| Use Case | Description | |---|---| | KOT (Kitchen Order Ticket) | Print order items to kitchen printer on order placement | | POS Receipt | Print customer receipts with itemized billing, taxes, totals | | Invoice | Print A4-style or thermal invoices with full order details | | Billing Slip | Quick billing printout for cashier stations | | Void / Cancel Slip | Print voided item or cancelled order slips for kitchen | | Shift Report | Print end-of-shift or daily summary reports | | Connection Test | Verify printer is reachable and print a test receipt |


Installation

npm install react-sunmi-thermal-printer

Peer dependency: React ≥ 17.0.0


Browser Requirement (One-Time Setup)

Because your web app is served over HTTPS but the Sunmi printer uses HTTP, Chrome blocks the request by default. You need to whitelist your printer's IP once:

  1. Open Chrome and go to: chrome://flags/#unsafely-treat-insecure-origin-as-secure
  2. Set the flag to Enabled
  3. Add your printer IP in the text field: http://192.168.1.100
  4. Click Relaunch

The example demo app includes a built-in step-by-step setup guide with copy buttons.


Quick Start

import { usePrinter, printerCommands, resolveCharsPerLine, resolveDotWidth } from "react-sunmi-thermal-printer";

function MyPOS() {
  const { print, isLoading } = usePrinter({
    ip: "192.168.1.100",
    serialNumber: "N4502482T0086",
    paperWidth: "80mm",
    copies: 1,
  });

  const printReceipt = async () => {
    const totalChars = resolveCharsPerLine("80mm"); // 48
    const dotWidth = resolveDotWidth("80mm");       // 576
    const sep = (c = "-") => printerCommands.appendText(c.repeat(totalChars) + "\n");

    // Define columns as dot-width slices: Qty(15%) | Item(55%) | Price(30%)
    const colWidths = printerCommands.setColumnWidths(
      printerCommands.columnWidthWithAlignment(Math.floor(dotWidth * 0.15), 0), // left
      printerCommands.columnWidthWithAlignment(Math.floor(dotWidth * 0.55), 0), // left
      printerCommands.columnWidthWithAlignment(Math.floor(dotWidth * 0.30), 2), // right
    );

    let hex = "";
    hex += printerCommands.setAlignment(1);
    hex += printerCommands.setFontSize("h7", true);
    hex += printerCommands.appendText("MY RESTAURANT\n");
    hex += printerCommands.setNormalFont();
    hex += sep("=");

    // Column header
    hex += printerCommands.setAlignment(0);
    hex += printerCommands.setFontSize("h8", true);
    hex += printerCommands.printInColumns(colWidths, "QTY", "ITEM", "PRICE");
    hex += printerCommands.setNormalFont();
    hex += sep();

    // Items — no manual spacing, columns handle alignment
    hex += printerCommands.printInColumns(colWidths, "2x", "Classic Burger", "10.00");
    hex += printerCommands.printInColumns(colWidths, "1x", "Caesar Salad",   "8.50");
    hex += sep();

    // Totals
    hex += printerCommands.setFontSize("h8", true);
    hex += printerCommands.printInColumns(colWidths, "",   "TOTAL:",          "18.50");
    hex += printerCommands.setNormalFont();
    hex += sep("=");
    hex += printerCommands.lineFeed(4);
    hex += printerCommands.cutPaper(true);

    await print(hex);
  };

  return <button onClick={printReceipt} disabled={isLoading}>Print Receipt</button>;
}

Connection Config

| Field | Type | Default | Description | |----------------|------------------------------|-----------|--------------------------------------| | ip | string | "" | Printer IP address (required) | | serialNumber | string | "" | Printer serial / SN number | | copies | number | 1 | Default number of copies | | paperWidth | "58mm" \| "80mm" \| string | "80mm" | Paper width — see Paper Width Guide | | timeout | number | 5000 | Request timeout in ms | | encoding | string | "UTF-8" | Text encoding |


Paper Width Guide

| Paper | Chars/line | Dot Width | Common Use | |--------|-------------------------|-----------|-----------------------| | 58mm | 32 | 384 | Small receipt printers| | 80mm | 48 | 576 | Standard POS printers | | Custom | Math.floor(mm * 0.53) | — | Pass as "72mm" etc. |


usePrinterStatus(config)

Checks printer reachability on demand — no auto-polling on mount. Call connect() yourself (e.g. on a save/connect button click).

const { isConnected, isLoading, error, connect } = usePrinterStatus({
  ip: "192.168.1.100",
  serialNumber: "N4502482T0086",
});

// Only fires when explicitly called:
<button onClick={connect}>Test Connection</button>

Returns

| Field | Type | Description | |---------------|----------------|---------------------------------------| | isConnected | boolean | Whether the printer responded | | isLoading | boolean | Connection check in progress | | error | string\|null | Error message if unreachable | | connect | () => Promise<void> | Trigger connection check — returns a Promise |


usePrinter(config)

Sends raw ESC/POS hex to the printer. Build any template using printerCommands.

const { print, isLoading, lastError, printerStatus } = usePrinter({
  ip: "192.168.1.100",
  serialNumber: "N4502482T0086",
  paperWidth: "80mm",
  copies: 1,
});

Returns

| Field | Type | Description | |-----------------|-----------------------------------|-------------------------------| | print | (hex: string, copies?: number) => Promise<void> | Send ESC/POS hex to printer | | isLoading | boolean | Print job in progress | | lastError | string\|null | Last error message | | printerStatus | object | Output of usePrinterStatus |


printerCommands

Stateless ESC/POS command builder. Every method returns a hex string — concatenate and pass to print().

import { printerCommands } from "react-sunmi-thermal-printer";

const hex =
  printerCommands.setAlignment(1) +           // center
  printerCommands.setFontSize("h6", true) +   // large bold
  printerCommands.appendText("KOT #5\n") +
  printerCommands.setNormalFont() +
  printerCommands.separator("-", 48) +
  printerCommands.setAlignment(0) +           // left
  printerCommands.appendText("2x Burger\n") +
  printerCommands.lineFeed(4) +
  printerCommands.cutPaper(true);

await print(hex);

Available Commands

| Method | Parameters | Description | |--------|-----------|-------------| | appendText(str) | str: string | Encode text to UTF-8 ESC/POS hex | | lineFeed(n?) | n: number = 1 | Feed n lines | | cutPaper(fullCut) | fullCut: boolean | Full or partial paper cut | | setAlignment(n) | n: 0\|1\|2 | 0=left, 1=center, 2=right | | setFontSize(size, bold?) | size: "h1"–"h8"\|"" | Font scale + optional bold | | setBoldFont(enabled) | enabled: boolean | Toggle bold only | | setNormalFont() | — | Reset font to default | | separator(char, length) | char: string, length: number | Print a separator line | | darkBackground(text) | text: string | Inverted black background text | | setColumnWidths(...values) | ...encoded: number[] | Returns column widths array for dot-based columns | | columnWidthWithAlignment(width, align) | width: number, align: 0\|1\|2 | Encode dot-width + alignment | | printInColumns(widths, ...texts) | widths: number[], texts: string[] | Print text in dot-positioned columns | | setAbsolutePrintPosition(n) | n: number | Set horizontal print position in dots |


Dynamic Column Helper

Define columns as percentage widths — they auto-resolve to character counts based on paper width. No dot math needed.

import { formatColumns, buildColumnLine, resolveCharsPerLine } from "react-sunmi-thermal-printer";

// Define columns as % of paper width
const columns = [
  { label: "Qty",   width: 15, align: "left"  },
  { label: "Item",  width: 60, align: "left"  },
  { label: "Price", width: 25, align: "right" },
];

// Resolve to actual char widths for 80mm (48 chars/line)
const resolved = formatColumns(columns, "80mm");
// → [{charWidth: 7}, {charWidth: 28}, {charWidth: 12}]

// Build a padded text line
const line = buildColumnLine(["2x", "Classic Burger", "12.99"], resolved);
// → "2x     Classic Burger              12.99"

// Use in a receipt
let hex = "";
hex += printerCommands.setAlignment(0);
hex += printerCommands.appendText(buildColumnLine(resolved.map(c => c.label), resolved) + "\n");
hex += printerCommands.separator("-", resolveCharsPerLine("80mm"));
items.forEach(item => {
  hex += printerCommands.appendText(
    buildColumnLine([`${item.qty}x`, item.name, item.price.toFixed(2)], resolved) + "\n"
  );
});

Helper Functions

| Function | Description | |---|---| | formatColumns(columns, paperWidth) | Resolve % widths → { charWidth, align, label }[] | | buildColumnLine(cells, resolvedCols) | Build a padded string line from cell values | | resolveCharsPerLine(paperWidth) | Get total characters per line for a paper width | | resolveDotWidth(paperWidth) | Get total dot width for ESC/POS absolute positioning | | padCell(text, width, align) | Pad a string to exactly N characters |


Building a KOT Template (Example)

The package doesn't include built-in templates — you build them with printerCommands. Here's a minimal KOT:

import {
  printerCommands,
  formatColumns,
  buildColumnLine,
  resolveCharsPerLine,
} from "react-sunmi-thermal-printer";

export function buildKOT({ storeName, kotNumber, tableNumber, items, paperWidth = "80mm" }) {
  const totalChars = resolveCharsPerLine(paperWidth);
  const sep = (c = "-") => printerCommands.appendText(c.repeat(totalChars) + "\n");

  const cols = formatColumns(
    [
      { label: "Qty",  width: 20, align: "left" },
      { label: "Item", width: 80, align: "left" },
    ],
    paperWidth
  );

  let hex = "";

  // Header
  hex += printerCommands.setAlignment(1);
  hex += printerCommands.setFontSize("h7", true);
  hex += printerCommands.appendText(`${storeName}\n`);
  hex += printerCommands.setNormalFont();
  hex += sep("=");

  // KOT info
  hex += printerCommands.setFontSize("h6", true);
  hex += printerCommands.appendText(`KOT No: ${kotNumber}\n`);
  hex += printerCommands.setNormalFont();
  if (tableNumber) {
    hex += printerCommands.appendText(`Table: ${tableNumber}\n`);
  }
  hex += sep();

  // Items
  hex += printerCommands.setAlignment(0);
  items.forEach((item) => {
    hex += printerCommands.setFontSize("h7", true);
    hex += printerCommands.appendText(
      buildColumnLine([`${item.qty}x`, item.name], cols) + "\n"
    );
    hex += printerCommands.setNormalFont();
    if (item.notes) {
      hex += printerCommands.appendText(`   Note: ${item.notes}\n`);
    }
  });

  hex += sep("=");
  hex += printerCommands.lineFeed(4);
  hex += printerCommands.cutPaper(true);

  return hex;
}

Then use it:

const { print } = usePrinter({ ip: "192.168.1.100", serialNumber: "N4502482T0086" });

await print(buildKOT({
  storeName: "My Kitchen",
  kotNumber: "42",
  tableNumber: "T3",
  items: [
    { qty: 2, name: "Burger",  notes: "No onion" },
    { qty: 1, name: "Fries" },
  ],
  paperWidth: "80mm",
}));

Running the Demo App

The example/ folder is a full Vite + React demo that shows every package function in action.

# 1. Build the package
cd react-sunmi-thermal-printer
npm install && npm run build

# 2. Run the demo
npm run example
# or:
cd example && npm install && npm run dev

Demo runs at http://localhost:5173 and includes:

  • Connection panel with saved settings display + Chrome setup guide
  • 7 function demo print buttons (alignment, font sizes, columns, column helper, dark background, separators, full receipt)
  • Custom raw print builder with live code preview
  • Live column helper character-width preview
  • Full package exports reference

Publishing

Auto (GitHub Actions)

Push a version tag — the workflow builds and publishes to npm automatically:

git tag v1.0.1 && git push origin v1.0.1

Add NPM_TOKEN in your GitHub repo → Settings → Secrets.

Manual

./scripts/publish.sh patch   # or: minor / major

Builds, bumps version, commits, tags, publishes, and pushes.


Contributing

  1. Fork the repo and create a feature branch
  2. Make changes in src/
  3. npm run build to verify
  4. Test with npm run example
  5. Open a pull request