npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

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

v1.0.9

Published

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

Readme

@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:

| Permission | API Level | Purpose | Required? | | ---------------------- | --------- | ---------------------------- | -------------------- | | BLUETOOTH_SCAN | 31+ | Scan for Bluetooth devices | ✅ Yes | | BLUETOOTH_CONNECT | 31+ | Connect to Bluetooth devices | ✅ Yes | | BLUETOOTH | ≤30 | Legacy Bluetooth access | ✅ Yes (old Android) | | BLUETOOTH_ADMIN | ≤30 | Legacy Bluetooth discovery | ✅ Yes (old Android) | | ACCESS_FINE_LOCATION | ≤30 | BLE scan on old Android | ✅ Yes (old Android) | | INTERNET | All | LAN/WiFi printing | ✅ Yes | | ACCESS_NETWORK_STATE | All | Check 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

| Type | Format | Example | | ----------------- | ------------- | ------------------------ | | Bluetooth Classic | bt:MAC | bt:AA:BB:CC:DD:EE:FF | | BLE | ble:MAC | ble:AA:BB:CC:DD:EE:FF | | LAN/WiFi | lan:IP:PORT | lan: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