Skip to content

mbugua70/react-native-receipt-printer

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

48 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

react-native-receipt-printer

npm version npm downloads license

A React Native library for thermal receipt printing over Bluetooth, with built-in permission handling, device connection management, and dynamic receipt building.


Contents


✨ Features

  • Bluetooth permissions — cross-platform, Android 12+ and legacy Android, iOS. Fully handled with one call.
  • Three-layer API — raw functions, React hooks, and Context providers. Pick the level that fits your app.
  • Device discovery — scan for nearby devices or list already-paired ones.
  • Connection management — connect, disconnect, and track connection state reactively.
  • ESC/POS thermal encoding — supports text, dividers, spacers, QR codes, and paper cut. 58mm and 80mm paper widths.
  • Fluent receipt builder — construct receipts with chainable methods or pass a JSON structure from your backend.
  • Print queue — jobs are processed one at a time. No corrupted output from concurrent prints.
  • Retry logic — automatically retries failed writes before giving up. Configurable per print call.
  • Print status events — subscribe to sending, done, and error events in real time.
  • usePrinter hookisPrinting, status, and error state wired up automatically.

📋 Requirements

React Native

Requirement Version
React Native 0.83.0+ (New Architecture / TurboModules)
React 19.2.0+

This library uses the New Architecture (TurboModules + JSI). The old architecture (Bridge) is not supported.

Android

Requirement Version
Minimum SDK API 24 (Android 7.0 Nougat)
Target SDK API 36 (Android 16)
Compile SDK API 36
Kotlin 2.1.20+

Bluetooth permission behaviour by Android version:

Android version API level Permissions required
Android 12+ API 31+ BLUETOOTH_CONNECT, BLUETOOTH_SCAN
Android 11 and below API ≤ 30 BLUETOOTH, BLUETOOTH_ADMIN, ACCESS_FINE_LOCATION

The library handles both automatically — you just call requestBluetoothPermissions() or ensureReady().

iOS

Requirement Version
Minimum deployment target iOS 15.1

NSBluetoothAlwaysUsageDescription is required in Info.plist (see iOS setup).

Peer dependencies

Package Version
react-native-permissions ^5.5.1
react-native-bluetooth-classic ^1.73.0-rc.17

📦 Installation

Expo users: This library requires native modules and is not compatible with Expo Go. Use a development build (expo-dev-client) or the bare workflow instead.

Install the library along with its peer dependencies:

yarn add react-native-receipt-printer react-native-permissions react-native-bluetooth-classic

or with npm:

npm install react-native-receipt-printer react-native-permissions react-native-bluetooth-classic

Why peer dependencies?

react-native-permissions and react-native-bluetooth-classic ship native Kotlin/Swift code. To prevent duplicate native modules — which crash React Native apps at build time — this library declares them as peer dependencies instead of bundling its own copies. You install them once in your app and the library uses your copy.

Android setup

Add the following to android/app/src/main/AndroidManifest.xml:

<!-- Bluetooth permissions for Android 12+ (API 31+) -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission
    android:name="android.permission.BLUETOOTH_SCAN"
    android:usesPermissionFlags="neverForLocation" />

<!-- Legacy Bluetooth for Android 11 and below -->
<uses-permission
    android:name="android.permission.BLUETOOTH"
    android:maxSdkVersion="30" />
<uses-permission
    android:name="android.permission.BLUETOOTH_ADMIN"
    android:maxSdkVersion="30" />

<!-- Required for Bluetooth scanning on Android 11 and below -->
<uses-permission
    android:name="android.permission.ACCESS_FINE_LOCATION"
    android:maxSdkVersion="30" />

iOS setup

Add the following to ios/YourApp/Info.plist:

<key>NSBluetoothAlwaysUsageDescription</key>
<string>This app uses Bluetooth to connect to receipt printers.</string>

Follow each peer dependency's native setup guide for additional configuration:


🖨️ Printing

Building a receipt

Use ReceiptBuilder to construct a receipt with chainable methods:

import { ReceiptBuilder, encode } from 'react-native-receipt-printer';

const receipt = new ReceiptBuilder({ paperWidth: 58 })
  .header('ACME RIDES')
  .divider()
  .row('Passenger:', 'John Doe')
  .row('Fare:', 'KES 500')
  .row('Date:', '2026-04-14')
  .divider()
  .qrCode('https://acme.com/receipt/123')
  .spacer(2)
  .cut()
  .build();
Method Description Defaults
.header(title, options?) Large bold centered title align: center, bold: true
.row(label, value) Two-column row — label left, value right
.text(content, options?) Single line of text align: left, size: normal
.divider(options?) Horizontal rule char: '-'
.spacer(lines?) Blank lines lines: 1
.qrCode(data) Centered QR code
.cut() Paper cut — always last
.build() Returns ReceiptData ready for encode()

Options you can override on .header() and .text():

.header('ACME RIDES', { align: 'left', bold: false })
.text('Note', { align: 'center', size: 'large', bold: true })

Backend-driven receipts

If your backend returns receipt data, pass it directly to encode() — no builder needed:

const data = await fetch('/api/receipt/123').then(r => r.json());
// data is already ReceiptData shape:
// { paperWidth: 58, lines: [ { type: 'text', ... }, ... ] }

encode(data); // → raw ESC/POS string

Printing

import { connect, print } from 'react-native-receipt-printer';

await connect('00:11:22:AA:BB:CC');
await print(receipt);                               // 1 copy, 2 retries
await print(receipt, { copies: 2 });               // 2 copies
await print(receipt, { copies: 1, retries: 5 });   // up to 5 retries
await print(receipt, { copies: 1, retries: 0 });   // no retries

PrintOptions:

Option Type Default Description
copies number 1 How many receipts to print
retries number 2 How many times to retry a failed write
retryDelayMs number 500 Milliseconds between retry attempts

usePrinter hook

The recommended way to print from a React component:

import { usePrinter } from 'react-native-receipt-printer';

function PrintButton({ receipt }) {
  const { print, isPrinting, status, error } = usePrinter();

  return (
    <>
      <Button
        title={isPrinting ? 'Printing...' : 'Print'}
        onPress={() => print(receipt)}
        disabled={isPrinting}
      />
      <Text>Status: {status}</Text>
      {error && <Text style={{ color: 'red' }}>{error.message}</Text>}
    </>
  );
}
Value Type Description
print(data, options?) function Send a receipt to the print queue
isPrinting boolean true while the job is in flight
status 'idle' | 'sending' | 'done' | 'error' Fine-grained current state
error Error | null Last error, or null if no error

Print queue

Jobs are always processed one at a time — safe to call print() or enqueue() multiple times without worrying about concurrent writes corrupting the output.

import { enqueue, clearQueue, getQueueLength } from 'react-native-receipt-printer';

// Add jobs — processed in order
enqueue(customerReceipt);
enqueue(driverReceipt, { copies: 2 });

// How many jobs are waiting (not counting the one currently printing)
getQueueLength();

// Cancel all pending jobs (the current job finishes normally)
clearQueue();

Print status events

Subscribe anywhere in your app to get real-time updates:

import { onPrintStatus } from 'react-native-receipt-printer';

const unsubscribe = onPrintStatus((event) => {
  if (event.status === 'sending') {
    console.log(`Sending copy ${event.copy} of ${event.totalCopies}...`);
  }
  if (event.status === 'done') {
    console.log(`Copy ${event.copy} printed successfully.`);
  }
  if (event.status === 'error') {
    console.log(`Copy ${event.copy} failed: ${event.error.message}`);
  }
});

// Unsubscribe when done (e.g. in useEffect cleanup)
unsubscribe();

Error handling

import {
  NotConnectedError,
  PrintError,
} from 'react-native-receipt-printer';

try {
  await print(receipt);
} catch (err) {
  if (err instanceof NotConnectedError) {
    // no device connected — show "connect to a printer" UI
  }
  if (err instanceof PrintError) {
    // write failed after all retries — err.cause has the underlying error
    console.log(err.cause);
  }
}

🔌 Bluetooth API

The library exposes three layers for Bluetooth. Pick the one that fits your use case.


Layer 1 — Raw functions

Maximum flexibility. Call directly from anywhere — inside or outside React.

Permissions

import {
  requestBluetoothPermissions,
  checkBluetoothPermissions,
} from 'react-native-receipt-printer';

// Shows the OS permission dialog if not yet granted
const result = await requestBluetoothPermissions();
// { granted: boolean, blocked: boolean, denied: Permission[] }

// Silent check — no dialog, safe for polling
const result = await checkBluetoothPermissions();

Bluetooth state

import {
  ensureReady,
  isBluetoothEnabled,
  requestBluetoothEnabled,
} from 'react-native-receipt-printer';

// Checks permissions + radio state in one call
const result = await ensureReady();
// { ready: true } or { ready: false, reason: 'permission_denied' | 'permission_blocked' | 'bluetooth_disabled' }

// Silent radio check
const enabled = await isBluetoothEnabled();

// Shows "Turn on Bluetooth?" system dialog
const enabled = await requestBluetoothEnabled();

Device discovery

import {
  getBondedDevices,
  startScan,
  stopScan,
  findDevice,
} from 'react-native-receipt-printer';

// Get already-paired devices — instant, no scan
const devices = await getBondedDevices();

// Scan for nearby unpaired devices — fires callback per device found
await startScan((device) => {
  console.log(device.name, device.address);
}, { timeoutMs: 10_000 });

// Stop scan early
await stopScan();

// Find by address (preferred) or name — checks bonded first, scans as fallback
const device = await findDevice({ address: '00:11:22:AA:BB:CC' });
const device = await findDevice({ name: 'EPSON-TM-T20' });

Connection

import {
  connect,
  disconnect,
  getConnectedDevice,
  isConnected,
} from 'react-native-receipt-printer';

// Always call ensureReady() before connecting
await ensureReady();
await connect('00:11:22:AA:BB:CC');

await disconnect();

const device = getConnectedDevice(); // BluetoothDevice | null
const connected = isConnected();     // boolean

Bluetooth events

Subscribe to real-time Bluetooth state changes — no polling required.

import {
  onConnectionChange,
  onBluetoothStateChange,
} from 'react-native-receipt-printer';

const unsub = onConnectionChange((event) => {
  if (event.type === 'connected')    console.log('Connected to:', event.device.name);
  if (event.type === 'disconnected') console.log('Disconnected');
});

const unsubState = onBluetoothStateChange((event) => {
  if (!event.enabled) console.log('Bluetooth was turned off');
});

// Always unsubscribe when done
unsub();
unsubState();

Note: If you use the useBluetooth hook or BluetoothProvider, events are already wired up internally.


Layer 2 — React hooks

useBluetoothPermissions

import { useBluetoothPermissions } from 'react-native-receipt-printer';

const {
  status,       // 'unknown' | 'granted' | 'denied' | 'blocked'
  granted,      // boolean
  blocked,      // boolean
  loading,      // boolean — true while checking
  request,      // () => Promise — shows OS dialog
  check,        // () => Promise — silent re-check
} = useBluetoothPermissions({
  requestOnMount: true,       // auto-prompt on first render
  recheckOnForeground: true,  // re-check when app returns to screen
});

if (status === 'blocked') return <OpenSettingsButton />;
if (!granted) return <Button title="Allow Bluetooth" onPress={request} />;
return <PrinterScreen />;

useBluetooth

import { useBluetooth } from 'react-native-receipt-printer';

const {
  isReady,            // ensureReady passed — safe to connect
  readyError,         // 'permission_denied' | 'permission_blocked' | 'bluetooth_disabled'
  bondedDevices,      // already-paired devices
  discoveredDevices,  // devices found during scan (builds in real time)
  connectedDevice,    // currently connected device or null
  isConnected,        // boolean
  isScanning,         // boolean
  isConnecting,       // boolean
  checkReady,         // () => Promise
  loadBondedDevices,  // () => Promise
  scan,               // (options?) => Promise
  cancelScan,         // () => Promise
  find,               // (query) => Promise<BluetoothDevice | null>
  connect,            // (address) => Promise
  disconnect,         // () => Promise
} = useBluetooth();

Example — connect to a printer:

const { isReady, readyError, bondedDevices, isConnected, checkReady, loadBondedDevices, connect } = useBluetooth();

useEffect(() => { checkReady(); }, []);
useEffect(() => { if (isReady) loadBondedDevices(); }, [isReady]);

if (readyError === 'permission_blocked') return <OpenSettingsButton />;
if (readyError === 'bluetooth_disabled') return <EnableBluetoothButton />;

return (
  <>
    {bondedDevices.map(device => (
      <TouchableOpacity key={device.address} onPress={() => connect(device.address)}>
        <Text>{device.name}</Text>
      </TouchableOpacity>
    ))}
    {isConnected && <Text>Connected ✓</Text>}
  </>
);

Layer 3 — Context providers

Provides shared state across your entire app. Use when multiple screens need the same Bluetooth state.

import {
  BluetoothPermissionProvider,
  BluetoothProvider,
  useBluetoothPermissionsContext,
  useBluetoothContext,
} from 'react-native-receipt-printer';

export default function App() {
  return (
    <BluetoothPermissionProvider requestOnMount>
      <BluetoothProvider>
        <Navigation />
      </BluetoothProvider>
    </BluetoothPermissionProvider>
  );
}

function PrinterStatus() {
  const { isConnected, connectedDevice } = useBluetoothContext();
  return <Text>{isConnected ? connectedDevice?.name : 'Not connected'}</Text>;
}

📱 Example app

A fully working demo app is included in the example/ directory. It covers the complete end-to-end flow in a single screen:

What it demos Where in code
Permission request + status display PermissionSection component
Bluetooth radio state + readiness check BluetoothSection component
List already-paired (bonded) devices loadBondedDevices() + device list
Live device scanning with real-time results scan() + discoveredDevices
Connect / disconnect to a device connect(address) / disconnect()
Live event log — connection + radio state changes EventLog component + onConnectionChange / onBluetoothStateChange

Run it yourself:

# Clone the repo
git clone https://github.com/mbugua70/react-native-receipt-printer.git
cd react-native-receipt-printer

# Install dependencies
yarn

# Run on Android
yarn example android

# Run on iOS
yarn example ios

The example app reads directly from the local src/ — no need to install from npm.


🗺️ Roadmap

  • Bluetooth permission handling (Android 12+, legacy Android, iOS)
  • Bluetooth radio state management
  • Device discovery (bonded + scan)
  • Connection management
  • Bluetooth events — connection and radio state changes
  • ESC/POS thermal encoder — text, dividers, spacers, QR codes, paper cut
  • 58mm / 80mm thermal paper width support
  • Fluent receipt builder
  • print() with copies support
  • Print queue — serial job processing
  • Retry logic — automatic retries with configurable delay
  • Print status events — sending, done, error
  • usePrinter hook — isPrinting, status, error
  • Receipt templates

💬 Feedback and contributions

If you try this library, feedback is welcome — especially around printer compatibility, Bluetooth connection handling, and receipt formatting.

Open an issue or start a discussion on GitHub.


📄 License

MIT

About

A React Native library for thermal receipt printing over Bluetooth, with device connection management and dynamic receipt building.

Resources

License

Code of conduct

Contributing

Stars

4 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors