react-native-earl-thermal-printer
v1.3.0
Published
A modern, high-performance thermal printer library for React Native. Built with the New Architecture (TurboModules) for synchronous communication, zero legacy bridge overhead, and Android 12+ Bluetooth compliance.
Downloads
191
Maintainers
Readme
react-native-earl-thermal-printer
A React Native library for USB, Bluetooth (BLE), and Network (TCP/IP) thermal receipt printers. Modern, high-performance thermal printer library for React Native. Built with the New Architecture (TurboModules) for synchronous communication, zero legacy bridge overhead, and Android 12+ Bluetooth compliance.
Built for the React Native New Architecture (TurboModules / Codegen). Supports Android and iOS.
Requirements
| Platform | Minimum Version | | ------------ | ------------------ | | React Native | >= 0.73 | | React | >= 18 | | Android SDK | 23 (compileSdk 34) | | iOS | 13.4+ |
Installation
npm install react-native-earl-thermal-printer
# or
yarn add react-native-earl-thermal-printeriOS
cd ios && pod installAndroid
No additional steps — the library auto-links via the React Native Gradle plugin.
Printer Support Matrix
| Feature | Android | iOS |
| -------------------- | :-----: | :----------------------------: |
| USB Printer | Yes | No (returns ERR_UNSUPPORTED) |
| BLE Printer | Yes | Yes |
| Net Printer | Yes | Yes |
| Print Text (ESC/POS) | Yes | Yes |
| Print Image | Yes | Yes |
| Print QR Code | Yes | Yes |
Quick Start
import {
USBPrinter,
BLEPrinter,
NetPrinter,
NetPrinterEventEmitter,
RN_THERMAL_RECEIPT_PRINTER_EVENTS,
} from "react-native-earl-thermal-printer";USB Printer (Android only)
await USBPrinter.init();
const devices = await USBPrinter.getDeviceList();
await USBPrinter.connectPrinter(devices[0].vendor_id, devices[0].product_id);
await USBPrinter.printText("Hello from USB!\n");
await USBPrinter.printBill("Receipt line\n");
await USBPrinter.printImage("https://example.com/logo.png", 300);
await USBPrinter.printQrCode("https://example.com", 200);
USBPrinter.closeConn();BLE Printer
await BLEPrinter.init();
const devices = await BLEPrinter.getDeviceList();
await BLEPrinter.connectPrinter(devices[0].inner_mac_address);
await BLEPrinter.printText("Hello from BLE!\n");
await BLEPrinter.printBill("Receipt line\n");
await BLEPrinter.printImage("https://example.com/logo.png", 300);
await BLEPrinter.printQrCode("https://example.com", 200);
BLEPrinter.closeConn();Net Printer
await NetPrinter.init();
// Listen for discovered printers on the local subnet
NetPrinterEventEmitter.addListener(
RN_THERMAL_RECEIPT_PRINTER_EVENTS.EVENT_NET_PRINTER_SCANNED_SUCCESS,
(printers) => console.log("Found:", printers),
);
const devices = await NetPrinter.getDeviceList();
await NetPrinter.connectPrinter("192.168.1.100", 9100);
await NetPrinter.printText("Hello from Network!\n");
await NetPrinter.printBill("Receipt line\n");
await NetPrinter.printImage("https://example.com/logo.png", 300);
await NetPrinter.printQrCode("https://example.com", 200);
NetPrinter.closeConn();API Reference
All three printer objects (USBPrinter, BLEPrinter, NetPrinter) share the same high-level interface:
init(): Promise<string>
Initialize the printer module. Must be called before any other method.
getDeviceList(): Promise<IUSBPrinter[] | IBLEPrinter[] | INetPrinter[]>
Scan for available printers and return a list of discovered devices.
connectPrinter(...): Promise<object>
Connect to a printer.
| Printer | Parameters |
| ------- | ------------------------------------- |
| USB | vendorId: number, productId: number |
| BLE | innerMacAddress: string |
| Net | host: string, port: number |
printText(text: string, opts?: PrinterOptions): Promise<void>
Print a text string using ESC/POS encoding. Supports formatting tags (see below).
printBill(text: string, opts?: PrinterOptions): Promise<void>
Same as printText but defaults beep, cut, and tailingLine to true.
printImage(imageUrl: string, imageWidth?: number): Promise<void>
Print an image from a URL. The optional imageWidth parameter controls the maximum width in pixels for the printed image (default: 200 on Android, 150 on iOS).
printQrCode(qrCode: string, qrSize?: number): Promise<void>
Print a QR code. The optional qrSize parameter controls the size in pixels of the generated QR code (default: 250).
closeConn(): void
Disconnect from the printer.
PrinterOptions
interface PrinterOptions {
beep?: boolean; // Beep after printing (default: false)
cut?: boolean; // Cut paper after printing (default: false)
tailingLine?: boolean; // Add trailing blank lines (default: false)
encoding?: string; // Text encoding (default: "UTF8")
}ESC/POS Formatting Tags
The text helpers (printText, printBill) support inline formatting tags.
All formatting resets automatically on every \n (newline), so tags apply per-line.
Text Style
| Tag | Description |
| ---------------------- | ---------------------------------------------------- |
| <BOLD>...</BOLD> | Bold / emphasis (no size change) |
| <U>...</U> | Underline (1-dot thin) |
| <U2>...</U2> | Underline (2-dot thick) |
| <REV>...</REV> | Reverse (white text on black background) |
| <UPDOWN>...</UPDOWN> | Upside-down printing |
Font Selection
| Tag | Description |
| ----------- | ------------------------------------------------------- |
| <FONT_A> | Select Font A — default, typically 12×24 dots |
| <FONT_B> | Select Font B — smaller, typically 9×17 dots |
Text Alignment
| Tag | Description |
| ------------ | -------------- |
| <L>...</L> | Left-aligned |
| <C>...</C> | Center-aligned |
| <R>...</R> | Right-aligned |
Font Size — Presets
These use the GS ! command for clean size multipliers (1×–8×).
| Tag | Width | Height | Description |
| -------------- | :---: | :----: | ------------------ |
| <W2>...</W2> | 2× | 1× | Wide |
| <W3>...</W3> | 3× | 1× | Extra-wide |
| <H2>...</H2> | 1× | 2× | Tall |
| <H3>...</H3> | 1× | 3× | Extra-tall |
| <X2>...</X2> | 2× | 2× | Double size |
| <X3>...</X3> | 3× | 3× | Triple size |
| <X4>...</X4> | 4× | 4× | Quadruple size |
Font Size — Custom
Use <FS:W,H> for arbitrary width/height multipliers (1–8):
<FS:2,3>Big text</FS> ← width ×2, height ×3
<FS:1,5>Very tall</FS> ← width ×1, height ×5
<FS:8,8>Maximum size</FS> ← width ×8, height ×8Spacing Control
| Tag | Description |
| ----------------------- | ------------------------------------------------------------ |
| <LINESPC:N> | Set line spacing to N dots (0–255). Close with </LINESPC>. |
| <CHARSPC:N> | Set character spacing to N dots (0–255). Close with </CHARSPC>. |
Legacy Size Tags
These use the older ESC ! command and are kept for backward compatibility.
| Tag | Description |
| -------------- | ---------------------------------------- |
| <B>...</B> | Big (double-height + double-width) |
| <D>...</D> | Double-width |
| <DB>...</DB> | Double-width + bold emphasis |
| <M>...</M> | Medium (double-height) |
| <CM>...</CM> | Center + medium |
| <CB>...</CB> | Center + big |
| <CD>...</CD> | Center + double-width |
Actions & Utilities
| Tag | Description |
| ------------ | ------------------------------------------------- |
| <PARTCUT> | Partial paper cut (mid-document) |
| <DRAWER> | Open cash drawer (ESC p pulse) |
| <TAB> | Insert horizontal tab (0x09) |
| <FEED:N> | Feed N blank lines (1–255) |
| <RESET> | Reset all formatting to defaults |
Raw Bytes
For power users who need direct ESC/POS control:
<RAW:1B,40> ← sends ESC @ (initialize printer)
<RAW:1D,56,00> ← sends GS V 0 (full cut, alternative)Combining Tags
Tags can be freely combined on the same line:
await NetPrinter.printBill(
"<C><BOLD><X2>MY STORE</X2></BOLD></C>\n" +
"<C><U>================================</U></C>\n" +
"<FONT_B>Item 1 $5.00\n" +
"Item 2 $3.50\n" +
"<FONT_A><BOLD>================================</BOLD>\n" +
"<R><FS:2,2>TOTAL $8.50</FS></R>\n" +
"<C><REV> THANK YOU! </REV></C>\n"
);Interfaces
interface IUSBPrinter {
device_name: string;
device_id: number;
vendor_id: number;
product_id: number;
}
interface IBLEPrinter {
device_name: string;
inner_mac_address: string;
}
interface INetPrinter {
device_name: string;
host: string;
port: number;
}Events
Net printer scanning emits events via NetPrinterEventEmitter:
| Event | Enum | Payload |
| ----------------- | ----------------------------------- | --------------- |
| scannerResolved | EVENT_NET_PRINTER_SCANNED_SUCCESS | INetPrinter[] |
| scannerRunning | EVENT_NET_PRINTER_SCANNING | boolean |
import {
NetPrinterEventEmitter,
RN_THERMAL_RECEIPT_PRINTER_EVENTS,
} from "react-native-earl-thermal-printer";
NetPrinterEventEmitter.addListener(
RN_THERMAL_RECEIPT_PRINTER_EVENTS.EVENT_NET_PRINTER_SCANNED_SUCCESS,
(printers) => {
console.log("Discovered printers:", printers);
},
);
NetPrinterEventEmitter.addListener(
RN_THERMAL_RECEIPT_PRINTER_EVENTS.EVENT_NET_PRINTER_SCANNING,
(isScanning) => {
console.log("Scanning:", isScanning);
},
);Android Permissions
Add these permissions to your AndroidManifest.xml:
<!-- USB -->
<uses-feature android:name="android.hardware.usb.host" android:required="false" />
<!-- Bluetooth -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- Network -->
<uses-permission android:name="android.permission.INTERNET" />Example
import React, { useEffect, useState } from "react";
import {
Alert,
Button,
FlatList,
PermissionsAndroid,
Platform,
StyleSheet,
Text,
TouchableOpacity,
View,
} from "react-native";
import { BLEPrinter, IBLEPrinter } from "react-native-earl-thermal-printer";
export default function ThermalPrinterTest() {
const [printers, setPrinters] = useState<IBLEPrinter[]>([]);
const [currentPrinter, setCurrentPrinter] = useState<IBLEPrinter | null>(
null,
);
useEffect(() => {
BLEPrinter.init()
.then(() => {
console.log("Printer initialized");
})
.catch((err) => {
console.warn("Init failed:", err);
});
}, []);
const requestPermissions = async () => {
if (Platform.OS === "android") {
try {
const granted = await PermissionsAndroid.requestMultiple([
PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
]);
if (
granted["android.permission.BLUETOOTH_CONNECT"] ===
PermissionsAndroid.RESULTS.GRANTED
) {
scanDevices();
} else {
Alert.alert("Permission denied");
}
} catch (err) {
console.warn(err);
}
} else {
scanDevices();
}
};
const scanDevices = () => {
BLEPrinter.getDeviceList()
.then(setPrinters)
.catch((err) => Alert.alert("Scan Failed", String(err)));
};
const connectPrinter = (printer: IBLEPrinter) => {
BLEPrinter.connectPrinter(printer.inner_mac_address)
.then((connected) => {
setCurrentPrinter(connected);
Alert.alert(
"Connected",
`Connected to ${connected.device_name}`,
);
})
.catch((err) => Alert.alert("Connection Failed", String(err)));
};
const printTicket = async () => {
if (!currentPrinter) {
Alert.alert("No Printer", "Please connect to a printer first.");
return;
}
try {
// To print a QR code:
await BLEPrinter.printQrCode("ZAM-OC-0001", 100); // qrSize
// Print formatted receipt text (beeps + cuts automatically)
const bill =
"--------------------------------\n" +
"Item 1 $10.00\n" +
"Item 2 $20.00\n" +
"--------------------------------\n" +
"<B>TOTAL $30.00</B>\n\n\n";
await BLEPrinter.printBill(bill);
// To print an image from URL:
// await BLEPrinter.printImage(
"https://images.unsplash.com/photo-1771258052747-52e19364185f?q=80&w=765&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
300, // imageWidth
);
} catch (err) {
console.warn("Print error:", err);
Alert.alert("Print Error", String(err));
}
};
return (
<View style={styles.container}>
<Button title="Scan for Printers" onPress={requestPermissions} />
<FlatList
data={printers}
keyExtractor={(item) => item.inner_mac_address}
renderItem={({ item }) => (
<TouchableOpacity
style={styles.deviceItem}
onPress={() => connectPrinter(item)}
>
<Text>{item.device_name || "Unknown Device"}</Text>
<Text style={styles.subText}>
{item.inner_mac_address}
</Text>
</TouchableOpacity>
)}
style={{ maxHeight: 200, marginVertical: 20 }}
/>
<View style={styles.printArea}>
<Text style={{ marginBottom: 10 }}>
Status:{" "}
{currentPrinter
? `Connected to ${currentPrinter.device_name}`
: "Disconnected"}
</Text>
<Button
title="Print Test Receipt"
onPress={printTicket}
disabled={!currentPrinter}
/>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 40, paddingTop: 60 },
deviceItem: { padding: 10, borderBottomWidth: 1, borderColor: "#ccc" },
subText: { fontSize: 10, color: "#666" },
printArea: { marginTop: 20, alignItems: "center" },
});Running the Example
A working example app lives in the example/ directory.
Prerequisites
- Node.js (>= 18)
- Yarn (v1) — required because the example uses
link:.. - Android Studio with an emulator or a physical device (USB debugging enabled)
- For iOS: Xcode with CocoaPods
Setup & Run
# 1. Install root dependencies
yarn install
# 2. Install example dependencies
cd example
yarn install
# 3. Run on Android
yarn android
# 4. (iOS) Install pods, then run
cd ios && pod install && cd ..
yarn iosWindows users: If the build fails with a
mkdir/ path error, the project path is too long for CMake. Usesubstto shorten it:subst P: "C:\path\to\react-native-thermal-receipt-printer" cd P:\example\android .\gradlew.bat app:assembleDebug
New Architecture
This library is built for the React Native New Architecture using TurboModules and Codegen. It requires:
- React Native >= 0.73
- New Architecture enabled in your app
The library ships codegen specs in src/Native*.ts. The native code is generated automatically during the build.
Author
Ordovez, Earl Romeo
License
ISC
Funding
If you find this library useful, consider sponsoring on GitHub.
