unified_esc_pos_printer 3.4.0 copy "unified_esc_pos_printer: ^3.4.0" to clipboard
unified_esc_pos_printer: ^3.4.0 copied to clipboard

Unified ESC/POS thermal printer package for Flutter. Supports USB, Bluetooth Classic, BLE, and Network connections with a single PrinterManager API

unified_esc_pos_printer #

A unified ESC/POS thermal printer package for Flutter. Supports USB, Bluetooth Classic, BLE, and Network connections through a single PrinterManager API. Includes a full ESC/POS command generator with text formatting, images, barcodes, QR codes, table layouts, cash drawer control, and multilingual text rasterization.

Features #

  • Unified API — Network (TCP/IP), Bluetooth Classic (SPP), BLE, and USB through a single PrinterManager
  • Device discovery — Scan for printers across all connection types simultaneously or filter by type
  • Text formatting — Bold, underline, reverse, alignment, 8 size multipliers, Font A/B
  • Table layouts — Flex-based multi-column rows with per-column styling and text wrapping
  • Raster table layouts — Flutter TextStyle-powered columns for any font, script, or style
  • Image printing — Column format (ESC*) and raster formats (GS v 0 / GS(L) with auto-resizing
  • Barcodes — UPC-A, UPC-E, EAN-13, EAN-8, CODE39, ITF, CODABAR, CODE128
  • QR codes — Native printer QR generation with 8 sizes and 4 error correction levels
  • Text rasterization — Print any script (Chinese, Japanese, Korean, Arabic RTL, etc.) as Flutter-rendered images
  • Cash drawer — Pin 2 and Pin 5 kick commands
  • Beep — Configurable buzzer count and duration
  • Capability profiles — 200+ built-in printer model profiles with code page mappings
  • Connection state tracking — Validated state transitions with broadcast streams
  • Typed exceptions — Granular exception hierarchy for connection, permission, write, and scan errors

Print result

Platform Support #

Connection Android iOS Windows Linux macOS Web
Network (TCP/IP) Yes Yes Yes Yes Yes No
Bluetooth Classic Yes No Yes No No No
BLE Yes Yes Yes No No No
USB Yes (OTG) No Yes (Print Spooler) Yes (serial) Yes (serial) No

Web: the package compiles on web so multi-platform apps can share code, but browsers expose no raw TCP, SPP, or USB access, so no connection type is functional there. scanPrinters() finds no devices and connect() throws PrinterConnectionException.

Android USB notes:

  • The Android device must support USB OTG (host mode). Most modern phones and tablets do.
  • The connector picks the right transport automatically based on the printer's USB interface class:
    • CDC / Virtual COM chips (FTDI, CP210x, PL2303, CH34x, USB CDC ACM) go through the usb_serial package.
    • USB Printer Class (interface class 0x07) — used by most generic ESC/POS thermal printers — goes through a native UsbManager + bulkTransfer path. The user will see a one-time Android USB permission dialog on first connect.
  • Vendor-specific (interface class 0xFF) printers with proprietary protocols aren't supported. Use Bluetooth or Network for those.

Tested Devices #

Device BLE Bluetooth Classic USB
RPP02N Yes Yes Yes

Getting Started #

Installation #

Add to your pubspec.yaml:

dependencies:
  unified_esc_pos_printer: any

Android Setup #

This package does not declare any permissions in its own manifest. You must add the permissions you need to your app's android/app/src/main/AndroidManifest.xml. This lets your app control its own maxSdkVersion gating — important if your app needs ACCESS_FINE_LOCATION on Android 12+ for other purposes (maps, geolocation, geofencing, etc.).

Add only the permissions for the connection types you use:

<!-- Network (TCP/IP printers) -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<!-- Bluetooth Classic / BLE — Android 11 and below (API ≤ 30) -->
<uses-permission android:name="android.permission.BLUETOOTH"
    android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
    android:maxSdkVersion="30" />

<!-- Bluetooth scan requires location on API ≤ 30.
     If your app does NOT otherwise need location on API 31+, add maxSdkVersion="30".
     If your app DOES need location for other features (maps, geolocation, etc.),
     omit the maxSdkVersion attribute. -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
    android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"
    android:maxSdkVersion="30" />

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

<!-- USB (OTG) -->
<uses-feature android:name="android.hardware.usb.host" android:required="false" />

Note on neverForLocation: the BLUETOOTH_SCAN declaration above uses usesPermissionFlags="neverForLocation" to tell Android that your app does not derive physical location from BLE scan results. Remove this flag if your app does use BLE scans to infer location — in that case you must also request ACCESS_FINE_LOCATION at runtime on API 31+.

iOS Setup #

This plugin supports both CocoaPods and Swift Package Manager. No setup is needed for either: Flutter 3.44+ uses SPM by default, and older versions fall back to CocoaPods automatically. To opt in to SPM on an earlier Flutter version, run flutter config --enable-swift-package-manager.

Add to ios/Runner/Info.plist:

<key>NSBluetoothAlwaysUsageDescription</key>
<string>Required to discover and connect to BLE printers.</string>
<key>NSLocalNetworkUsageDescription</key>
<string>Required to discover network printers.</string>

Windows / Linux / macOS #

No additional configuration required for network printers. USB serial ports work out of the box via flutter_libserialport. On Windows, USB printers are accessed through the Print Spooler API.

Quick Start #

import 'package:unified_esc_pos_printer/unified_esc_pos_printer.dart';

final manager = PrinterManager();

// Scan for printers (all connection types, 5-second timeout)
final printers = await manager.scanPrinters(
  timeout: const Duration(seconds: 5),
);

// Connect to the first discovered printer
await manager.connect(printers.first);

// Build a ticket
final ticket = await Ticket.create(PaperSize.mm80);
ticket.text(
  'Hello, Printer!',
  align: PrintAlign.center,
  style: const PrintTextStyle(
    bold: true,
    height: TextSize.size2,
    width: TextSize.size2,
  ),
);
ticket.cut();

// Print and clean up
await manager.printTicket(ticket);
await manager.disconnect();
manager.dispose();

Usage #

Text Formatting #

ticket.text('Normal text');
ticket.text('Bold text', style: const PrintTextStyle(bold: true));
ticket.text('Underline', style: const PrintTextStyle(underline: true));
ticket.text('Reverse', style: const PrintTextStyle(reverse: true));
ticket.text('Bold + Underline', style: const PrintTextStyle(bold: true, underline: true));

// Size multipliers (1x–8x)
ticket.text('Large', style: const PrintTextStyle(height: TextSize.size3, width: TextSize.size3));
ticket.text('Tall only', style: const PrintTextStyle(height: TextSize.size3, width: TextSize.size1));

// Alignment
ticket.text('Left', align: PrintAlign.left);
ticket.text('Center', align: PrintAlign.center);
ticket.text('Right', align: PrintAlign.right);

// Font selection
ticket.text('Font A', style: const PrintTextStyle(fontType: FontType.fontA));
ticket.text('Font B', style: const PrintTextStyle(fontType: FontType.fontB));

// Per-line code table override (must exist in the loaded capability profile)
ticket.text('Café coûté 12,50 €', style: const PrintTextStyle(codeTable: 'CP1252'));

Table Layouts #

Use row() with PrintColumn for multi-column layouts. Columns use flex-based proportional sizing and support per-column PrintTextStyle:

// 2-column receipt layout
ticket.row([
  PrintColumn(text: 'Espresso', flex: 2),
  PrintColumn(
    text: '\$3.50',
    flex: 1,
    align: PrintAlign.right,
    style: const PrintTextStyle(bold: true),
  ),
]);

// 3-column layout with header
ticket.row([
  PrintColumn(text: 'Item', flex: 5, style: const PrintTextStyle(bold: true)),
  PrintColumn(
    text: 'Qty',
    flex: 3,
    align: PrintAlign.center,
    style: const PrintTextStyle(bold: true),
  ),
  PrintColumn(
    text: 'Total',
    flex: 4,
    align: PrintAlign.right,
    style: const PrintTextStyle(bold: true),
  ),
]);

Raster Table Layouts #

Use rowRaster() with PrintRasterColumn for multi-column layouts rendered using Flutter's text engine. This supports any font, script, or TextStyle that Flutter can render — ideal for CJK, Arabic, or custom-styled columns:

// 2-column receipt layout with Flutter TextStyle
await ticket.rowRaster([
  PrintRasterColumn(text: 'Cappuccino', flex: 2),
  PrintRasterColumn(text: '\$4.50', flex: 1, align: PrintAlign.right),
]);

// Styled columns
await ticket.rowRaster([
  PrintRasterColumn(
    text: 'Bold Title',
    flex: 1,
    style: const TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
  ),
  PrintRasterColumn(text: 'Normal', flex: 1, align: PrintAlign.right),
]);

// Multilingual columns
await ticket.rowRaster([
  PrintRasterColumn(text: '商品名', flex: 2),
  PrintRasterColumn(text: '数量', flex: 1, align: PrintAlign.center),
  PrintRasterColumn(text: '价格', flex: 1, align: PrintAlign.right),
]);

// RTL column
await ticket.rowRaster([
  PrintRasterColumn(
    text: 'السعر',
    flex: 1,
    textDirection: TextDirection.rtl,
    align: PrintAlign.right,
  ),
  PrintRasterColumn(text: 'Item', flex: 2),
]);

Separators #

ticket.separator();            // ------------------------------------------------
ticket.separator(char: '=');   // ================================================
ticket.separator(char: '*');   // ************************************************

Images #

import 'package:image/image.dart' as img;

// Load and print an image (raster mode)
final bytes = await rootBundle.load('assets/logo.png');
final image = img.decodeImage(bytes.buffer.asUint8List())!;

ticket.imageRaster(
  image,
  align: PrintAlign.center,
  maxWidth: 400,
  maxHeight: 200,
);

// Column format (ESC*)
ticket.image(image, align: PrintAlign.center);

Barcodes #

Supported types: UPC-A, UPC-E, EAN-13, EAN-8, CODE39, ITF, CODABAR, CODE128.

ticket.barcode(
  '590123412345',
  type: BarcodeType.ean13,
  textPosition: BarcodeTextPosition.below,
);

ticket.barcode(
  '{BABCDEF12345',
  type: BarcodeType.code128,
  textPosition: BarcodeTextPosition.below,
);

QR Codes #

Native printer QR generation with sizes 1–8 and error correction levels L/M/Q/H:

ticket.qrcode('https://example.com', size: QRSize.size5);

ticket.qrcode(
  'https://example.com',
  size: QRSize.size8,
  cor: QRCorrection.H,  // 30% error recovery
);

Text Rasterization #

For scripts not supported by the printer's built-in character tables (CJK, Arabic, Devanagari, Thai, etc.), use textRaster() which renders text using Flutter's text engine and prints it as an image:

await ticket.textRaster('欢迎光临,谢谢惠顾!');       // Chinese
await ticket.textRaster('ようこそ、ありがとう!');     // Japanese
await ticket.textRaster('환영합니다, 감사합니다!');    // Korean
await ticket.textRaster('स्वागत है, धन्यवाद!');          // Hindi

// RTL support
await ticket.textRaster(
  'مرحبا — نص عريض كبير',
  textDirection: TextDirection.rtl,
  align: PrintAlign.right,
);

// Custom styling
await ticket.textRaster(
  'Large Bold Text',
  style: const TextStyle(fontSize: 36, fontWeight: FontWeight.bold),
  align: PrintAlign.center,
);

Cash Drawer & Beep #

// Open cash drawer
ticket.openCashDrawer();                      // Pin 2 (default)
ticket.openCashDrawer(pin: CashDrawer.pin5);  // Pin 5

// Beep
ticket.beep(n: 3, duration: BeepDuration.beep100ms);

Scanning by Connection Type #

// Scan all connection types
final all = await manager.scanPrinters();

// Scan specific types only
final networkOnly = await manager.scanPrinters(
  types: {PrinterConnectionType.network},
);

// Stream-based scanning for progressive results
manager.scanAll(timeout: const Duration(seconds: 5)).listen((devices) {
  print('Found ${devices.length} printers so far');
});

Connecting to Known Devices #

// Network printer by IP
await manager.connect(NetworkPrinterDevice(
  name: 'Kitchen Printer',
  host: '192.168.1.100',
  port: 9100,
));

// BLE printer
await manager.connect(BlePrinterDevice(
  name: 'BLE Printer',
  deviceId: 'AA:BB:CC:DD:EE:FF',
));

// Bluetooth Classic printer
await manager.connect(BluetoothPrinterDevice(
  name: 'BT Printer',
  address: '11:22:33:44:55:66',
));

Connection State Management #

PrinterManager exposes a broadcast stream of connection states:

manager.stateStream.listen((state) {
  switch (state) {
    case PrinterConnectionState.disconnected:
      print('Disconnected');
    case PrinterConnectionState.scanning:
      print('Scanning...');
    case PrinterConnectionState.connecting:
      print('Connecting...');
    case PrinterConnectionState.connected:
      print('Connected');
    case PrinterConnectionState.printing:
      print('Printing...');
    case PrinterConnectionState.disconnecting:
      print('Disconnecting...');
    case PrinterConnectionState.error:
      print('Error');
  }
});

// Quick checks
if (manager.isConnected) { ... }
final device = manager.connectedDevice;

Waiting for Writes to Complete #

On some transports (Bluetooth Classic, BLE write-without-response, USB serial), printTicket() resolves once the data is accepted by an OS buffer, not when it has physically reached the printer. disconnect() automatically waits for that buffer to drain, so printing followed immediately by a disconnect does not truncate the job:

await manager.printTicket(ticket);
await manager.disconnect(); // waits for buffered data to drain first

When you need the barrier without disconnecting (e.g. printing several tickets and reporting completion between them), use waitWriteComplete():

await manager.printTicket(ticket);
await manager.waitWriteComplete(); // resolves when the data has had time to reach the printer

The drain time is estimated from the amount of data written and a conservative link throughput. For Bluetooth Classic you can tune it via the connector:

final manager = PrinterManager(
  bluetoothConnector: BluetoothConnector(
    drainBytesPerSecond: 16 * 1024,              // your printer's real throughput
    maxDrainWait: const Duration(seconds: 5),     // cap on any single wait
  ),
);

BLE Write Pacing #

BLE printers often forward data to the print MCU over a small internal UART without flow control. A GATT write ACK only means the radio accepted the packet — not that the printer has drained it. Pushing a large ticket too fast (especially multilingual textRaster bitmaps) overflows that buffer and shows up as garbled / weird characters. Classic Bluetooth is usually less sensitive because RFCOMM buffers better.

BleConnector paces writes by default:

Parameter Default Meaning
chunkSize 128 Bytes per GATT write (0 = full negotiated MTU)
bytesPerSecond 6 * 1024 Target throughput used to delay between chunks

These defaults are a practical middle ground for common cheap ESC/POS BLE modules. They are not guaranteed for every printer — models differ. If output still garbles (often only on large / full tickets), slow the transfer:

final manager = PrinterManager(
  bleConnector: BleConnector(
    chunkSize: 64,
    bytesPerSecond: 4 * 1024, // lower = safer, slower
  ),
);

If a printer handles faster transfers cleanly, you can raise bytesPerSecond (e.g. 8 * 1024). Tune per device: increase until output garbles, then step back.

Concurrent Print Jobs #

Print jobs cannot interrupt each other:

  • Within one isolate, PrinterManager serializes its operations. A connect() or disconnect() issued while another job is printing waits for that job to finish, and connect() to the already-connected device is a no-op. Calling printTicket() from multiple places without external locking is safe; the jobs print one after the other.
  • Across isolates on Android (e.g. printing from a background notification handler while the main app prints too), the Bluetooth Classic, BLE, and USB printer-class connections are shared process-wide. A second isolate connecting to the same printer reuses the live connection and its job queues behind the one in progress. Connecting to a different printer while another isolate holds the connection throws a PrinterConnectionException with a printer-busy message; retry after the other job finishes.

Each print job is delivered to the platform as a single atomic write, so even jobs from separate isolates cannot interleave their bytes.

The package provides a typed exception hierarchy:

try {
  await manager.connect(device);
  await manager.printTicket(ticket);
} on PrinterConnectionException catch (e) {
  print('Connection failed: ${e.message}');
} on PrinterWriteException catch (e) {
  print('Write failed: ${e.message}');
} on PrinterPermissionException catch (e) {
  print('Permission denied: ${e.message}');
} on PrinterNotFoundException catch (e) {
  print('Device not found: ${e.message}');
} on PrinterScanException catch (e) {
  print('Scan failed: ${e.message}');
} on PrinterStateException catch (e) {
  print('Invalid state: ${e.currentState} → ${e.requiredState}');
} on PrinterException catch (e) {
  print('Printer error: ${e.message}');
}

Raw Bytes #

For advanced use cases, send raw ESC/POS command bytes:

// Via Ticket
ticket.rawBytes([0x1B, 0x40]); // ESC @ (initialize)

// Directly to printer
await manager.printBytes([0x1B, 0x40]);

API Reference #

Class Description
PrinterManager Unified facade for scanning, connecting, printing, and cash drawer control
Ticket High-level ticket builder — text, images, barcodes, QR codes, tables
Generator Low-level ESC/POS command generator returning raw byte sequences
PrintTextStyle Immutable text style configuration (bold, underline, size, font)
PrintColumn Column definition for table rows with flex-based sizing
PrintRasterColumn Column definition for raster table rows with Flutter TextStyle support
CapabilityProfile Printer capability and code page profile loader
PrinterDevice Abstract base for NetworkPrinterDevice, BlePrinterDevice, BluetoothPrinterDevice, UsbPrinterDevice
PrinterConnectionState Connection state enum with validated transitions
PrinterException Base exception with subclasses for connection, write, permission, scan, state errors
PrinterConnector Abstract connector interface (Network, BLE, Bluetooth, USB implementations)

Paper Sizes #

Size Width (pixels) Font A (chars/line) Font B (chars/line)
58 mm 384 32 42
72 mm 512 42 56
80 mm 576 48 64

Capability Profiles #

The package bundles capability profiles for 200+ thermal printer models, each defining supported ESC/POS code pages. Profiles are loaded automatically when creating a ticket:

// Auto-loads the default bundled profile
final ticket = await Ticket.create(PaperSize.mm80);

// Or load a specific profile
final profile = await CapabilityProfile.load(
  'Generic',
  jsonString: '{"profiles":{"default":{"vendor":"Generic","name":"Generic",'
              '"description":"Generic ESC/POS","codePages":{"0":"CP437"}}}}',
);

final ticket = Ticket(PaperSize.mm80, additionalProfile: profile);

Example App #

A full-featured demo app is included in the example/ directory. It demonstrates:

  • Scanning and filtering by connection type
  • Connecting/disconnecting with state feedback
  • Printing text styles, sizes, alignment, and fonts
  • Multi-column table layouts (2, 3, and 4 columns)
  • Raster row & columns with Flutter TextStyle (multilingual, custom fonts)
  • Text rasterization for CJK, Arabic, Hindi, Thai, Russian, and European scripts
  • Barcode and QR code generation
  • Image printing (assets and programmatic)
  • Cash drawer and beep commands

Run the example:

cd example
flutter run

License #

BSD 3-Clause License. Copyright (c) 2026, Elriz Technology.

See LICENSE for details.

5
likes
160
points
1.53k
downloads

Documentation

API reference

Publisher

verified publisherelriztechnology.com

Weekly Downloads

Unified ESC/POS thermal printer package for Flutter. Supports USB, Bluetooth Classic, BLE, and Network connections with a single PrinterManager API

Repository (GitHub)
View/report issues

Topics

#esc-pos #esc-pos-printer #thermal-printer #receipt-printer #printer-manager

License

BSD-3-Clause (license)

Dependencies

flutter, image, libserialport_plus, network_info_plus, usb_serial

More

Packages that depend on unified_esc_pos_printer

Packages that implement unified_esc_pos_printer