Big News: Socket Selected for OpenAI's Cybersecurity Grant Program.Details
Socket
Book a DemoSign in
Socket

@finan-me/react-native-thermal-printer

Package Overview
Dependencies
Maintainers
2
Versions
10
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@finan-me/react-native-thermal-printer

React Native Thermal Printer Library with ESC/POS, CPCL, TSPL support

latest
npmnpm
Version
1.0.9
Version published
Maintainers
2
Created
Source

@finan-me/react-native-thermal-printer

React Native library for ESC/POS thermal printers with Bluetooth, BLE, and LAN support.

Features

  • Multi-connection: Bluetooth Classic, BLE, LAN/WiFi
  • Vietnamese support: Full CP1258 encoding
  • Rich printing: Text, images, QR, barcodes, tables
  • Multi-printer: Print to multiple printers concurrently
  • Margin & Alignment: Consistent margins across all printer types
  • Cross-platform: Android & iOS

Installation

yarn add @finan-me/react-native-thermal-printer

iOS Setup

cd ios && pod install

Add to Info.plist:

<!-- Required: Bluetooth access for connecting to printers -->
<key>NSBluetoothAlwaysUsageDescription</key>
<string>This app needs Bluetooth access to connect to thermal printers for receipt printing</string>

<!-- iOS 13+: Required for discovering BLE printers -->
<key>NSBluetoothPeripheralUsageDescription</key>
<string>This app needs Bluetooth to discover and connect to thermal printers</string>

Notes:

  • iOS primarily uses BLE for thermal printers
  • Bluetooth Classic requires MFi certification (most printers don't have)
  • User will see permission prompt on first Bluetooth access

Android Setup

Add to AndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <!-- ========== NETWORK PERMISSIONS ========== -->
    <!-- Required: For LAN/WiFi printing -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

    <!-- ========== ANDROID 12+ (API 31+) ========== -->
    <!-- Required: Scan for Bluetooth devices -->
    <uses-permission
        android:name="android.permission.BLUETOOTH_SCAN"
        android:usesPermissionFlags="neverForLocation"
        tools:targetApi="31" />

    <!-- Required: Connect to Bluetooth devices -->
    <uses-permission
        android:name="android.permission.BLUETOOTH_CONNECT"
        tools:targetApi="31" />

    <!-- ========== ANDROID 11 AND BELOW (API ≤30) ========== -->
    <!-- Required: Legacy Bluetooth permissions -->
    <uses-permission
        android:name="android.permission.BLUETOOTH"
        android:maxSdkVersion="30" />
    <uses-permission
        android:name="android.permission.BLUETOOTH_ADMIN"
        android:maxSdkVersion="30" />

    <!-- Required: BLE scan requires location on API ≤30 -->
    <uses-permission
        android:name="android.permission.ACCESS_FINE_LOCATION"
        android:maxSdkVersion="30" />

</manifest>

Permission Explanations:

PermissionAPI LevelPurposeRequired?
BLUETOOTH_SCAN31+Scan for Bluetooth devices✅ Yes
BLUETOOTH_CONNECT31+Connect to Bluetooth devices✅ Yes
BLUETOOTH≤30Legacy Bluetooth access✅ Yes (old Android)
BLUETOOTH_ADMIN≤30Legacy Bluetooth discovery✅ Yes (old Android)
ACCESS_FINE_LOCATION≤30BLE scan on old Android✅ Yes (old Android)
INTERNETAllLAN/WiFi printing✅ Yes
ACCESS_NETWORK_STATEAllCheck network connectivity✅ Yes

neverForLocation Flag:

<uses-permission
    android:name="android.permission.BLUETOOTH_SCAN"
    android:usesPermissionFlags="neverForLocation"
    tools:targetApi="31" />
  • Purpose: Tells Android you DON'T use Bluetooth for location tracking
  • Effect: User won't see "Location" in permission prompt
  • When to use: When you ONLY scan Bluetooth for printers (not for location)

Without neverForLocation: ❌ "App wants to access Bluetooth and Location" With neverForLocation: ✅ "App wants to access Nearby devices"

Runtime Permissions:

import {PermissionsAndroid, Platform} from 'react-native'

async function requestBluetoothPermissions() {
  if (Platform.OS === 'android') {
    if (Platform.Version >= 31) {
      // Android 12+
      const granted = await PermissionsAndroid.requestMultiple([
        PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
        PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
      ])

      return (
        granted['android.permission.BLUETOOTH_SCAN'] === 'granted' &&
        granted['android.permission.BLUETOOTH_CONNECT'] === 'granted'
      )
    } else {
      // Android 11 and below
      const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION)
      return granted === 'granted'
    }
  }

  return true // iOS handles automatically
}

// Use before scanning
const hasPermission = await requestBluetoothPermissions()
if (hasPermission) {
  await ThermalPrinter.scanDevices()
}

Quick Start

import {ThermalPrinter} from '@finan-me/react-native-thermal-printer'

// 1. Scan devices
const {paired, found} = await ThermalPrinter.scanDevices()

// 2. Print receipt
const job = {
  printers: [
    {
      address: 'bt:AA:BB:CC:DD:EE:FF',
      options: {
        paperWidthMm: 58,
        encoding: 'CP1258', // Vietnamese
        marginMm: 1, // 1mm margin each side (default)
      },
    },
  ],
  documents: [
    [
      // Header
      {type: 'text', content: 'COFFEE SHOP', style: {align: 'center', bold: true, size: 'double'}},
      {type: 'text', content: '123 Main St', style: {align: 'center'}},
      {type: 'line'},

      // Table
      {
        type: 'table',
        headers: ['Item', 'Qty', 'Price'],
        rows: [
          ['Cappuccino', '2', '90.000đ'],
          ['Sandwich', '1', '35.000đ'],
        ],
        columnWidths: [50, 20, 30],
        alignments: ['left', 'center', 'right'],
      },

      // Total
      {type: 'line'},
      {type: 'text', content: 'TOTAL: 125.000đ', style: {bold: true, size: 'double_width'}},

      // QR payment
      {type: 'qr', content: 'https://payment.link/123', size: 6, align: 'center'},

      // Footer
      {type: 'text', content: 'Cảm ơn quý khách!', style: {align: 'center'}},
      {type: 'feed', lines: 3},
      {type: 'cut'},
    ],
  ],
}

await ThermalPrinter.printReceipt(job)

Address Format

TypeFormatExample
Bluetooth Classicbt:MACbt:AA:BB:CC:DD:EE:FF
BLEble:MACble:AA:BB:CC:DD:EE:FF
LAN/WiFilan:IP:PORTlan:192.168.1.100:9100

Supported Content Types

Text: {type: 'text', content: 'Hello', style: {align: 'center', bold: true, size: 'double'}}

Line: {type: 'line'}

Table: {type: 'table', headers: ['A', 'B'], rows: [['1', '2']], columnWidths: [50, 50]}

Columns: {type: 'columns', columns: [{content: 'Left', width: 50}, {content: 'Right', width: 50, align: 'right'}]}

QR Code: {type: 'qr', content: 'https://...', size: 6, align: 'center'}

Barcode: {type: 'barcode', content: '123456', format: 'CODE128', align: 'center'}

Image: {type: 'image', imagePath: '/path/to/image.png', options: {align: 'center', marginMm: 2}}

Feed: {type: 'feed', lines: 3}

Spacer: {type: 'spacer', height: 2, fill: '-'}

Cut: {type: 'cut', partial: true}

Options

Printer Options

{
  paperWidthMm?: 32 | 58 | 80,             // default: 58
  encoding?: 'CP1258' | 'UTF8' | 'ASCII',  // default: CP1258
  marginMm?: number,                        // default: 1mm each side
  keepAlive?: boolean
}

Job Options

{
  concurrent?: boolean,        // print to multiple printers in parallel
  continueOnError?: boolean,   // continue if one printer fails
  onProgress?: (completed: number, total: number) => void,
  onJobComplete?: (address: string, success: boolean) => void
}

Print Configuration

{
  address: string,
  copies?: number,              // number of copies (default: 1)
  delayBetweenCopies?: number,  // delay in ms (default: 200)
  options?: PrinterOptions
}

Multi-Printer Printing

const job = {
  printers: [
    {address: 'bt:11:11:11:11:11:11', copies: 2}, // Kitchen: 2 copies
    {address: 'lan:192.168.1.100:9100'}, // Counter: 1 copy
  ],
  documents: [[{type: 'text', content: 'Order #123'}, {type: 'cut'}]],
  options: {
    concurrent: true, // Print in parallel
    continueOnError: true,
    onProgress: (completed, total) => console.log(`${completed}/${total}`),
  },
}

const result = await ThermalPrinter.printReceipt(job)
// result.success, result.results (per-printer status)

Vietnamese Support

{
  options: {
    encoding: 'CP1258'
  }
}

Error Handling

try {
  await ThermalPrinter.printReceipt(job)
} catch (error) {
  console.log(error.code) // E1001, E2001, E4003...
  console.log(error.message) // Human readable
  console.log(error.suggestion) // How to fix
  console.log(error.retryable) // Can retry?
}

Troubleshooting

Vietnamese not printing?

  • Use encoding: 'CP1258'
  • Test with testCodepages() utility

Connection timeout?

  • Check printer is on and in range
  • Use testConnection() before printing

Image not printing?

  • Use local file path (not base64)
  • Images auto-resize to paper width

License

MIT

Keywords

react-native

FAQs

Package last updated on 27 Nov 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts