@azarudeen-ahamed/neo-pms-printer
v0.1.8
Published
Capacitor plugin for POS printing supporting Bluetooth, USB, WiFi, and built-in printers (Sunmi/PAX/Newland/iMin)
Downloads
294
Maintainers
Readme
@azarudeen-ahamed/neo-pms-printer
Capacitor plugin for POS (Point of Sale) printing. Supports Bluetooth Classic, Bluetooth LE, USB OTG, WiFi/Network, System Print Dialog, and Built-in POS printers (Sunmi, PAX, Newland, iMin).
Features
- 🖨️ Built-in POS Printers — Auto-detect Sunmi, PAX, Newland, iMin handheld devices
- 📶 Bluetooth Classic — SPP profile for thermal printers (Android)
- 🔵 Bluetooth LE — BLE thermal printers (Android & iOS)
- 🔌 USB Printers — USB Host API for direct USB connection (Android)
- 🌐 WiFi / Network Printers — TCP socket printing to IP-based printers
- 🖨️ System Print Dialog — AirPrint (iOS), PrintManager (Android)
- 📄 ESC/POS Protocol — Full receipt builder (text, QR, barcode, images, paper cut, cash drawer)
- 🌍 Web Fallback —
window.print()for PWA/browser usage
Installation
npm install @azarudeen-ahamed/neo-pms-printer
npx cap syncPlatform Support
| Feature | Android | iOS | |---------|---------|-----| | Built-in POS (Sunmi/PAX/Newland/iMin) | ✅ | ❌ (Android hardware) | | Bluetooth Classic (SPP) | ✅ | ❌ (MFi required) | | Bluetooth LE (BLE) | ✅ | ✅ | | USB OTG | ✅ | ❌ | | WiFi / Network (TCP) | ✅ | ✅ | | System Print Dialog | ✅ PrintManager | ✅ AirPrint | | Web (PWA) | ✅ window.print() | ✅ window.print() |
Quick Start
import { PMSPrinter } from '@azarudeen-ahamed/neo-pms-printer';
// 1. Discover all available printers
const { printers } = await PMSPrinter.discoverPrinters();
console.log('Found printers:', printers);
// 2. Connect to a printer
await PMSPrinter.connect({ printerId: printers[0].id });
// 3. Print a receipt
await PMSPrinter.printReceipt({
printerId: printers[0].id,
header: 'My Store',
items: [
{ name: 'Pizza', qty: 2, price: 12.99 },
{ name: 'Coke', qty: 1, price: 2.50 },
],
footer: 'Thank you! Visit again!',
paperWidth: 80,
cutPaper: true,
});Full API Reference
discoverPrinters()
Lists all available printers across all connection types.
const { printers } = await PMSPrinter.discoverPrinters();
// printers: PrinterInfo[]startDiscovery(options?)
Scans for BLE printers in range. Emits printerDiscovered events for each found device.
PMSPrinter.addListener('printerDiscovered', (event) => {
console.log('Found:', event.printer);
});
await PMSPrinter.startDiscovery({
timeout: 10000, // ms, default 10000
});stopDiscovery()
Stops an active BLE scan.
await PMSPrinter.stopDiscovery();connect(options)
Connects to a printer by ID.
await PMSPrinter.connect({ printerId: '00:11:22:33:44:55' }); // Bluetooth MAC
await PMSPrinter.connect({ printerId: 'built-in' }); // Built-in POS
await PMSPrinter.connect({ printerId: '192.168.1.100:9100' }); // Network TCPdisconnect(options)
Disconnects from a printer.
await PMSPrinter.disconnect({ printerId: '00:11:22:33:44:55' });printReceipt(options)
Prints a formatted thermal receipt using ESC/POS commands.
await PMSPrinter.printReceipt({
printerId: 'built-in',
header: 'My Store', // Store name / header text
items: [
{ name: 'Product A', qty: 2, price: 9.99 },
{ name: 'Product B', qty: 1, price: 19.99 },
],
footer: 'Thank you!', // Footer message
qrCode: 'https://example.com', // Auto-printed QR code
barcode: '123456789012', // Auto-printed barcode
paperWidth: 80, // 58 or 80 mm
showLogo: true, // Print logo image
logoBase64: 'data:image/png;base64,...', // Logo image (base64)
copies: 2, // Number of copies
cutPaper: true, // Auto-cut after print
});printRaw(options)
Sends raw ESC/POS byte commands (hex-encoded).
// ESC/POS: Initialize printer + print text + cut paper
await PMSPrinter.printRaw({
printerId: '00:11:22:33:44:55',
data: '1B40' + // Initialize
'1B6131' + // Center align
'48656C6C6F' + // "Hello" in hex
'1B6403' + // Feed 3 lines
'1D5600', // Cut paper
encoding: 'cp437',
feedLines: 3,
cutPaper: true,
});printHtml(options)
Renders and prints HTML content.
await PMSPrinter.printHtml({
printerId: '00:11:22:33:44:55', // Omit for system dialog
html: '<h1>Invoice</h1><p>Hello World</p>',
orientation: 'portrait', // or 'landscape'
paperSize: { width: 80, height: 297, unit: 'mm' },
copies: 1,
});printImage(options)
Prints an image to a thermal printer using ESC/POS raster format.
await PMSPrinter.printImage({
printerId: 'built-in',
imageBase64: 'data:image/png;base64,iVBOR...',
width: 384, // Pixel width
align: 'center', // left, center, right
paperWidth: 80,
});printViaSystemDialog(options)
Opens the system print dialog (AirPrint on iOS, PrintManager on Android).
await PMSPrinter.printViaSystemDialog({
html: '<h1>Document</h1><p>Content here</p>',
name: 'Invoice #1234',
orientation: 'portrait',
copies: 2,
monochrome: false,
});getPrinterStatus(options)
Gets the connection status of a specific printer.
const status = await PMSPrinter.getPrinterStatus({
printerId: 'built-in',
});
// status: PrinterStatusgetBuiltInPrinterStatus()
Gets detailed status of built-in POS printer (Sunmi/PAX/Newland/iMin).
const status = await PMSPrinter.getBuiltInPrinterStatus();
// status: BuiltInPrinterStatus
// { hasBuiltInPrinter: true, manufacturer: 'sunmi', model: 'S2', isReady: true, paperStatus: 'ok' }printParkingReceipt(options)
Prints a formatted parking receipt with exception handling support.
await PMSPrinter.printParkingReceipt({
printerId: 'built-in',
logoBase64: 'iVBORw0KGgo...', // Logo image as base64 string
logoPath: '/storage/emulated/0/logo.jpeg', // OR local file path
// logoPath: 'https://example.com/logo.png', // OR server URL
header: 'NEOLYSI PARKING',
date: 'May 20, 2026',
ticketNo: 'TKT-001234',
vehicleType: 'Car',
vehicleNo: 'TN 01 AB 1234',
driverName: 'John Doe',
mobileNo: '9876543210',
inTime: '10:30 AM',
outTime: '2:45 PM',
parkingType: 'PREPAID', // PREPAID or POSTPAID
duration: '4h 15m',
amount: '50.00',
footer: 'Thank You - Drive Safe',
additionalFareDetails: 'Overnight surcharge applied',
paperWidth: 58, // 58 or 80 mm
copies: 1,
cutPaper: true,
qrCode: JSON.stringify({ ticketId: 1234, vehicleNo: 'TN 01 AB 1234' }),
isException: false, // Mark as exception ticket
exceptionType: '', // e.g. 'Lost Ticket', 'Damaged Vehicle'
});getConnectedPrinters()
Lists all currently connected printers.
const { printers } = await PMSPrinter.getConnectedPrinters();Events
// Fired when a BLE printer is discovered during scan
PMSPrinter.addListener('printerDiscovered', (event: PrinterDiscoveredEvent) => {
console.log('Printer:', event.printer);
});
// Fired when a printer connects or disconnects
PMSPrinter.addListener('connectionStateChanged', (event: ConnectionStateChange) => {
console.log('State:', event.printerId, event.state);
});
// Fired when print job progress changes
PMSPrinter.addListener('printJobStatus', (event: PrintJobStatus) => {
console.log('Job:', event.jobId, event.status, event.progress);
});
// Clean up when leaving page
PMSPrinter.removeAllListeners();Type Definitions
type ConnectionType = 'bluetooth_classic' | 'bluetooth_le' | 'wifi' | 'usb' | 'airprint' | 'built_in';
type PrinterType = 'thermal' | 'laser' | 'inkjet' | 'label' | 'unknown';
type BuiltInManufacturer = 'sunmi' | 'pax' | 'newland' | 'imin' | 'unknown';
type PaperWidth = 58 | 80;
type PrintOrientation = 'portrait' | 'landscape';
type PrintJobState = 'queued' | 'printing' | 'completed' | 'failed';
type ConnectionState = 'connected' | 'disconnected' | 'connecting' | 'error';
type PaperStatus = 'ok' | 'low' | 'empty' | 'cover_open' | 'overheated' | 'disconnected';
interface PrinterInfo {
id: string;
name: string;
address?: string;
connectionType: ConnectionType;
printerType: PrinterType;
manufacturer?: string;
model?: string;
isConnected: boolean;
}
interface PrinterStatus {
printerId: string;
isConnected: boolean;
connectionType: ConnectionType;
isReady: boolean;
paperStatus?: PaperStatus;
batteryLevel?: number;
error?: string;
}
interface BuiltInPrinterStatus {
hasBuiltInPrinter: boolean;
manufacturer: BuiltInManufacturer;
model: string;
sdkVersion?: string;
isReady: boolean;
paperStatus?: PaperStatus;
}
interface ReceiptItem {
name: string;
qty: number;
price: number;
}
interface ReceiptOptions {
printerId: string;
header?: string;
items?: ReceiptItem[];
footer?: string;
qrCode?: string;
barcode?: string;
paperWidth: PaperWidth;
showLogo?: boolean;
logoBase64?: string;
copies?: number;
cutPaper?: boolean;
}
interface PrintRawOptions {
printerId: string;
data: string; // Hex-encoded ESC/POS bytes
encoding?: 'cp437' | 'cp850' | 'utf8';
feedLines?: number;
cutPaper?: boolean;
}
interface PrintHtmlOptions {
printerId?: string; // Omit to use system print dialog
html: string;
paperSize?: { width: number; height: number; unit: 'mm' | 'inch' | 'pt' };
orientation?: PrintOrientation;
copies?: number;
}
interface PrintImageOptions {
printerId: string;
imageBase64: string;
width?: number;
height?: number;
align?: 'left' | 'center' | 'right';
paperWidth?: PaperWidth;
}
interface SystemPrintOptions {
html?: string;
filePath?: string;
name?: string;
orientation?: PrintOrientation;
copies?: number;
monochrome?: boolean;
}
interface ParkingReceiptOptions {
printerId: string;
logoBase64?: string; // Logo image as base64 string
logoPath?: string; // OR local file path or HTTP(S) URL (auto-converted to base64)
header?: string;
date?: string;
ticketNo?: string;
vehicleType?: string;
vehicleNo?: string;
driverName?: string;
mobileNo?: string;
inTime?: string;
outTime?: string;
parkingType?: string;
duration?: string;
amount?: string;
footer?: string;
additionalFareDetails?: string;
paperWidth: PaperWidth;
copies?: number;
cutPaper?: boolean;
qrCode?: string;
isException?: boolean;
exceptionType?: string;
}
interface DiscoveryOptions {
timeout?: number;
connectionTypes?: ConnectionType[];
}Platform-Specific Setup
Android Permissions
Add to android/app/src/main/AndroidManifest.xml (auto-added by the plugin):
<!-- Bluetooth -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<!-- USB -->
<uses-feature android:name="android.hardware.usb.host" android:required="false" />
<!-- Network -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!-- Location (required for BT scan on Android 10-11) -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" />iOS Permissions
Add to ios/App/App/Info.plist:
<key>NSBluetoothAlwaysUsageDescription</key>
<string>This app uses Bluetooth to connect and print to thermal printers</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>This app uses Bluetooth to communicate with thermal printers</string>
<key>NSLocalNetworkUsageDescription</key>
<string>This app needs local network access to discover and print to WiFi printers</string>
<key>NSBonjourServices</key>
<array>
<string>_pdl-datastream._tcp</string>
<string>_printer._tcp</string>
<string>_ipp._tcp</string>
</array>Real-World Usage Examples
1. Built-in POS Printer (Sunmi/PAX/Newland/iMin)
// No need to connect — built-in printers are auto-detected
const status = await PMSPrinter.getBuiltInPrinterStatus();
if (status.hasBuiltInPrinter) {
console.log(`Detected: ${status.manufacturer} ${status.model}`);
await PMSPrinter.printReceipt({
printerId: 'built-in',
header: 'My Restaurant',
items: [
{ name: 'Fried Rice', qty: 2, price: 8.99 },
{ name: 'Chicken Wings', qty: 1, price: 12.50 },
],
footer: 'Table 5 · Server: John',
paperWidth: 80,
cutPaper: true,
});
}2. Bluetooth Printer Discovery + Connect + Print
import { PMSPrinter } from '@azarudeen-ahamed/neo-pms-printer';
// Step 1: Get paired BT devices
const { printers } = await PMSPrinter.discoverPrinters();
// Filter for Bluetooth printers
const btPrinters = printers.filter(p =>
p.connectionType === 'bluetooth_classic' || p.connectionType === 'bluetooth_le'
);
if (btPrinters.length > 0) {
// Step 2: Connect
await PMSPrinter.connect({ printerId: btPrinters[0].id });
// Step 3: Print raw ESC/POS
await PMSPrinter.printRaw({
printerId: btPrinters[0].id,
data: '1B40' + // Initialize
'1D2100' + // Normal size
'1B6100' + // Left align
'48656C6C6F20576F726C64' + // "Hello World" in hex
'1B6405' + // Feed 5 lines
'1D5600', // Cut paper
cutPaper: true,
});
// Step 4: Disconnect
await PMSPrinter.disconnect({ printerId: btPrinters[0].id });
}3. BLE Scanning with Real-Time Discovery
PMSPrinter.addListener('printerDiscovered', (event) => {
const printer = event.printer;
console.log(`Discovered: ${printer.name} [${printer.address}]`);
});
await PMSPrinter.startDiscovery({ timeout: 15000 });
// 15 seconds later, scan stops automatically4. Network/WiFi Printer
// Connect to a printer on the local network
await PMSPrinter.connect({ printerId: '192.168.1.200' }); // Port 9100 default
await PMSPrinter.connect({ printerId: '192.168.1.200:9100' }); // Explicit port
await PMSPrinter.printReceipt({
printerId: '192.168.1.200',
header: 'Office Receipt',
items: [{ name: 'Service', qty: 1, price: 100 }],
paperWidth: 80,
cutPaper: true,
});5. USB Printer (Android)
const { printers } = await PMSPrinter.discoverPrinters();
const usbPrinters = printers.filter(p => p.connectionType === 'usb');
if (usbPrinters.length > 0) {
await PMSPrinter.connect({ printerId: usbPrinters[0].id });
await PMSPrinter.printRaw({
printerId: usbPrinters[0].id,
data: '1B40',
});
}6. System Print Dialog (for documents)
const receiptHtml = `
<html>
<body style="font-family: monospace;">
<h1 style="text-align: center;">Invoice #1234</h1>
<hr/>
<p><strong>Date:</strong> ${new Date().toLocaleDateString()}</p>
<p><strong>Customer:</strong> John Doe</p>
<table style="width: 100%;">
<tr><th>Item</th><th>Qty</th><th>Price</th></tr>
<tr><td>Widget A</td><td>2</td><td>$19.99</td></tr>
</table>
<hr/>
<p style="text-align: right;"><strong>Total: $39.98</strong></p>
</body>
</html>
`;
await PMSPrinter.printViaSystemDialog({
html: receiptHtml,
name: 'Invoice #1234',
orientation: 'portrait',
});7. Angular Service Example
// print.service.ts
import { Injectable } from '@angular/core';
import { PMSPrinter } from '@azarudeen-ahamed/neo-pms-printer';
import type { PrinterInfo, ReceiptItem } from '@azarudeen-ahamed/neo-pms-printer';
@Injectable({ providedIn: 'root' })
export class PrintService {
async getBuiltInPrinter() {
return PMSPrinter.getBuiltInPrinterStatus();
}
async printReceipt(
printerId: string,
header: string,
items: ReceiptItem[],
footer: string,
) {
return PMSPrinter.printReceipt({
printerId,
header,
items,
footer,
paperWidth: 80,
cutPaper: true,
});
}
async getStatus() {
return PMSPrinter.getConnectedPrinters();
}
}8. React Hook Example
// usePrinter.ts
import { useState, useEffect } from 'react';
import { PMSPrinter } from '@azarudeen-ahamed/neo-pms-printer';
import type { PrinterInfo } from '@azarudeen-ahamed/neo-pms-printer';
export function usePrinterDiscovery() {
const [printers, setPrinters] = useState<PrinterInfo[]>([]);
const [scanning, setScanning] = useState(false);
useEffect(() => {
PMSPrinter.addListener('printerDiscovered', (event) => {
setPrinters(prev => {
if (prev.find(p => p.id === event.printer.id)) return prev;
return [...prev, event.printer];
});
});
return () => { PMSPrinter.removeAllListeners(); };
}, []);
return {
printers,
scanning,
discover: async () => {
const result = await PMSPrinter.discoverPrinters();
setPrinters(result.printers);
},
scan: async () => {
setScanning(true);
await PMSPrinter.startDiscovery({ timeout: 10000 });
setTimeout(() => setScanning(false), 10000);
},
};
}ESC/POS Command Reference
The plugin supports these ESC/POS commands (used internally by printReceipt and available via printRaw):
| Command | Hex | Description |
|---------|-----|-------------|
| Initialize | 1B 40 | Reset printer |
| Align Left | 1B 61 00 | Set alignment to left |
| Align Center | 1B 61 01 | Set alignment to center |
| Align Right | 1B 61 02 | Set alignment to right |
| Bold On | 1B 45 01 | Enable bold text |
| Bold Off | 1B 45 00 | Disable bold text |
| Underline On | 1B 2D 01 | Enable underline |
| Underline Off | 1B 2D 00 | Disable underline |
| Font Size | 1D 21 n | Set character size |
| Feed Lines | 1B 64 n | Feed n lines |
| Cut Paper | 1D 56 00 | Full cut |
| Partial Cut | 1D 56 01 | Partial cut (tear) |
| QR Code | 1D 28 6B ... | Print QR code |
| Barcode | 1D 6B 41 ... | Print barcode (UPC-A) |
| Image | 1D 76 30 ... | Print raster image |
| Cash Drawer | 1B 70 00 19 FA | Open cash drawer |
Troubleshooting
Printer not discovered
- Ensure Bluetooth is enabled and location permission granted (Android 10+)
- For USB: ensure USB OTG is supported and cable connected
- For network: verify printer IP and port (default 9100)
Connection fails
- Verify printer is powered on and in range
- For Bluetooth: unpair and re-pair the device
- For USB: accept the USB permission prompt on Android
iOS limitations
- Bluetooth Classic (SPP) is NOT supported on iOS (Apple requires MFi certification)
- USB printing is NOT supported on iOS
- Use BLE-capable thermal printers or AirPrint for iOS
Print quality issues
- Use
paperWidth: 80for 80mm printers,paperWidth: 58for 58mm - Images should be high-contrast for best thermal print results
- For logo printing, use simple black-and-white PNG images
Complete Ionic Angular Integration
app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, FormsModule],
bootstrap: [AppComponent],
})
export class AppModule {}app.component.ts
import { Component } from '@angular/core';
import { PMSPrinter } from '@azarudeen-ahamed/neo-pms-printer';
@Component({
selector: 'app-root',
template: `
<button (click)="print()">Print Receipt</button>
<div *ngIf="status">{{ status | json }}</div>
`,
})
export class AppComponent {
status: any;
async print() {
const { printers } = await PMSPrinter.discoverPrinters();
const builtIn = await PMSPrinter.getBuiltInPrinterStatus();
if (builtIn.hasBuiltInPrinter) {
await PMSPrinter.printReceipt({
printerId: 'built-in',
header: 'My Store',
items: [{ name: 'Item 1', qty: 1, price: 9.99 }],
paperWidth: 80,
cutPaper: true,
});
} else if (printers.length > 0) {
await PMSPrinter.connect({ printerId: printers[0].id });
await PMSPrinter.printReceipt({
printerId: printers[0].id,
header: 'My Store',
items: [{ name: 'Item 1', qty: 1, price: 9.99 }],
paperWidth: 80,
cutPaper: true,
});
}
}
}Complete Ionic React Integration
import React, { useState } from 'react';
import { PMSPrinter } from '@azarudeen-ahamed/neo-pms-printer';
function PrintButton() {
const [status, setStatus] = useState('');
const handlePrint = async () => {
try {
setStatus('Discovering printers...');
const { printers } = await PMSPrinter.discoverPrinters();
const builtIn = await PMSPrinter.getBuiltInPrinterStatus();
const printerId = builtIn.hasBuiltInPrinter
? 'built-in'
: printers[0]?.id;
if (!printerId) {
setStatus('No printer found');
return;
}
setStatus('Printing...');
await PMSPrinter.printReceipt({
printerId,
header: 'My Store',
items: [{ name: 'Product', qty: 1, price: 19.99 }],
paperWidth: 80,
cutPaper: true,
});
setStatus('Printed successfully!');
} catch (err: any) {
setStatus(`Error: ${err.message}`);
}
};
return (
<div>
<button onClick={handlePrint}>Print Receipt</button>
<p>{status}</p>
</div>
);
}License
MIT
