⚠️ Android Only: This package only works on Android devices.
A React Native library for printing with Sunmi external printers via TCP/IP, Bluetooth, and USB connections. Tested primarily on Sunmi Cloud Printer NT311.
Promise.all() for multiple print operations.
- 🖨️ Multiple Connection Types: TCP/IP, Bluetooth, and USB printing
- 🖼️ Image Printing: Support for base64 encoded images with multiple rendering methods
- 🔍 Device Discovery: Network and Bluetooth device scanning
- 🎨 HTML to Image: Convert HTML content to printable images
- 💰 Cash Drawer: Open cash drawers connected to printers
- 🔧 Print Management: Semaphore locking for print queue management
npm install sunmi-external-printerAdd the following permissions to your android/app/src/main/AndroidManifest.xml:
<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" />
<uses-permission android:name="android.permission.INTERNET" />import {
startNetworkDiscovery,
EscPosImageWithTCPConnectionRasterBitImageWrapper,
printImageByBluetooth,
scanBLDevice,
convertHTMLtoBase64,
} from 'sunmi-external-printer';
import { DeviceEventEmitter } from 'react-native';
// Network Discovery
const discoverPrinters = async () => {
await startNetworkDiscovery();
DeviceEventEmitter.addListener('OnPrinterFound', (event) => {
console.log('Found printer:', event.printername, event.ip, event.port);
});
};
// Print via TCP/IP
const printViaTCP = async () => {
const result = await EscPosImageWithTCPConnectionRasterBitImageWrapper(
base64Image,
'192.168.1.100',
'9100'
);
console.log('Print result:', result);
};interface printerDevice {
name: string;
address: string; // MAC address for Bluetooth devices
}
interface usbPrinterDevice {
id: string;
name: string;
productName: string;
manufacturerName: string;
vendorId: string;
version: string;
productId: string;
}// Start discovery
await startNetworkDiscovery();
DeviceEventEmitter.addListener('OnPrinterFound', (event) => {
// event: { printername: string, ip: string, port: string }
});
// Stop discovery
await stopNetworkDiscovery();
DeviceEventEmitter.removeAllListeners('OnPrinterFound');All methods accept: base64Image, ipAddress, port, and optional cut parameter.
// Recommended - Raster bit image (best quality)
await EscPosImageWithTCPConnectionRasterBitImageWrapper(
base64Image,
'192.168.1.100',
'9100',
'FULL'
);
// Alternative methods
await EscPosImageWithTCPConnectionBitImageWrapper(base64Image, ip, port);
await EscPosImageWithTCPConnectionGraphicsImageWrapper(base64Image, ip, port);// Request permissions first
import { PermissionsAndroid } from 'react-native';
await PermissionsAndroid.requestMultiple([
PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
]);
// Scan for devices (takes ~12 seconds)
const devices = await scanBLDevice();
// Get paired devices
const pairedDevices = await getPairedDevices();
// Print
await printImageByBluetooth(device, base64Image, 'FULL');// Discover USB devices
const usbDevices = await searchUSBDevices();
// Print
await printUSBDevice(device.productId, device.vendorId, base64Image, 'FULL');// TCP/IP
await openDrawer('192.168.1.100', '9100');
// Bluetooth
await openDrawerBluetooth(device);
// USB
await openDrawerUSB(productID, vendorID);const base64Image = await convertHTMLtoBase64(
`<html><body><h1>Receipt</h1><p>Total: $25.00</p></body></html>`,
400 // width in pixels
);await lockPrintingSemaphore();
try {
await printImageByBluetooth(device, image);
} finally {
await unlockPrintingSemaphore();
}await closePrinterSocket();
await closeTCPPrinterSocket();- Width: ~576 pixels for 80mm thermal printers
- Format: High contrast black and white images
- Base64: Remove
data:image/...;base64,prefix
try {
const result = await EscPosImageWithTCPConnectionRasterBitImageWrapper(
base64Image,
ipAddress,
port
);
} catch (error) {
console.error('Print failed:', error);
}- Remove event listeners on component unmount
- Close connections when done
- Use semaphore locking to prevent concurrent operations
| Issue | Solution |
|---|---|
| Bluetooth permission denied | Grant BLUETOOTH_SCAN, BLUETOOTH_CONNECT, ACCESS_FINE_LOCATION |
| Network printer not found | Check same network, port 9100 accessibility |
| Image not printing | Remove base64 prefix, check image width (~576px) |
| USB issues | Verify OTG support, USB permissions |
MIT
Made with create-react-native-library Prints to a USB connected printer.
| Parameter | Type | Required | Description |
|---|---|---|---|
productID |
string | Yes | USB product ID |
vendorID |
string | Yes | USB vendor ID |
base64 |
string | Yes | Base64 encoded image |
cut |
string | Yes | Cut type ('PARTIAL' or 'FULL') |
const result = await printUSBDevice(
device.productId,
device.vendorId,
base64Image,
'FULL'
);Opens cash drawer connected to network printer.
await openDrawer('192.168.1.100', '9100');Opens cash drawer via Bluetooth.
await openDrawerBluetooth(device);Opens cash drawer via USB.
await openDrawerUSB(productID, vendorID);Converts HTML content to a base64 image for printing.
| Parameter | Type | Required | Description |
|---|---|---|---|
html |
string | Yes | HTML content |
width |
number | Yes | Image width in pixels |
const base64Image = await convertHTMLtoBase64(
`
<html>
<body style="font-family: Arial;">
<h1>Receipt</h1>
<p>Total: $25.00</p>
</body>
</html>
`,
400
);Manage print queue to prevent overlapping print jobs.
await lockPrintingSemaphore();
try {
await printImageByBluetooth(device, image);
} finally {
await unlockPrintingSemaphore();
}Close printer connections.
await closePrinterSocket(); // General connection
await closeTCPPrinterSocket(); // TCP specificSend cut or feed commands via Bluetooth.
await printBLCut(device); // Cut paper
await printBLFeed(device); // Feed paper- Width: Optimize images for 80mm thermal printers (approximately 576 pixels)
- Format: Use high contrast black and white images for best results
- Size: Keep file sizes small to improve printing speed
Always wrap print operations in try-catch blocks:
try {
const result = await EscPosImageWithTCPConnectionRasterBitImageWrapper(
base64Image,
ipAddress,
port
);
console.log('Print successful:', result);
} catch (error) {
console.error('Print failed:', error);
}- Remove event listeners when component unmounts
- Close connections when done
- Use semaphore locking for concurrent print operations
Bluetooth Permission Denied
- Ensure all Bluetooth permissions are granted
- Target Android SDK 31+ requires BLUETOOTH_SCAN and BLUETOOTH_CONNECT
Network Printer Not Found
- Verify printer and device are on same network
- Check if printer port 9100 is accessible
- Ensure printer supports ESC/POS commands
Image Not Printing
- Remove
data:image/...;base64,prefix from base64 string - Ensure image width is appropriate for printer (80mm ≈ 576px)
- Try different image rendering methods
USB Printer Issues
- Verify USB OTG support on device
- Check USB permissions in Android manifest
- Ensure printer supports USB printing
See the /example folder for a complete implementation demonstrating all features.
See the contributing guide to learn how to contribute to the repository and the development workflow.
MIT
Made with create-react-native-library