universal_thermal_printer 1.0.0 copy "universal_thermal_printer: ^1.0.0" to clipboard
universal_thermal_printer: ^1.0.0 copied to clipboard

Universal Thermal Printer SDK for Flutter — Bluetooth & Network (WiFi/LAN) ESC/POS thermal printer support with text, QR, barcode, images, and built-in remote license activation.

universal_thermal_printer #

A production-grade Flutter SDK for thermal printers across any industry — retail, restaurants, logistics, pharmacy, banking, parking, warehousing and more. Bluetooth Classic + BLE + USB + Network, a composable template engine with industry-ready layouts, a built-in print preview, auto-reconnect, an offline queue, real-time status, and transparent licensing.

The SDK is designed to feel like a natural part of your app: connect a printer, pick a template, preview it, print. No license key is ever shown to your end users — licensing is an internal detail you configure once (or not at all).


Highlights #

  • Every transport: Bluetooth Classic (SPP), Bluetooth LE, USB (Android host), Network (WiFi/LAN, port 9100), with auto-discovery so users never type an IP.
  • Multi-printer routing: hold a receipt and a kitchen printer at once and send each job to the right one (PrinterManager).
  • Resilience: automatic reconnect with backoff, an offline print queue that flushes on reconnect, real-time status (paper out / cover open / offline / low battery), and typed exceptions.
  • Paper-aware templates: 58mm and 80mm profiles + label vs continuous stock; the template engine adapts column widths automatically.
  • Composable industry elements: TableElement, TaxBreakdownElement, AddressBlockElement, BarcodeLabelElement, BigNumberElement — plus 8 ready-to-use PrebuiltTemplates.
  • Live preview widget: ReceiptPreview renders a template on-screen exactly as it will print.
  • Pluggable command language: ESC/POS today; ZPL/TSPL/CPCL can be added later without changing your code (CommandProtocol).

Install #

dependencies:
  universal_thermal_printer: ^1.0.0

Licensing — handled for you #

You do not pass a license key on every call, and your users never see one. Configure the SDK once in main():

void main() {
  // Unlimited printing — use the license key you received (see "Get a license"):
  LicenseManager.instance.configure(licenseKey: 'UTP-XXXX-XXXX-XXXX');
  runApp(MyApp());
}

If you don't provide a key, the SDK runs in free mode: it still prints, but is capped at 10 bills per day (resets daily, tracked on-device). That's enough for evaluation and very small shops. Control operations (paper feed, cut, cash drawer) never count against the limit.

// Zero config also works — starts in free mode automatically:
LicenseManager.instance.configure();

Check state any time (e.g. to show a banner):

LicenseManager.instance.mode;           // LicenseMode.free / .licensed
LicenseManager.instance.remainingToday; // free prints left today, -1 = unlimited

A verified license keeps working offline: after a successful check, a transient network error is honored for a grace period (default 72h, configure(graceDuration: ...)), so a flaky connection never blocks a checkout.

Get a license / support #

To remove the daily limit, request a license key. For keys, pricing or any support:

📧 Contact: shilpa.devloper@gmail.com (SDK owner: set this once in LicenseManager.supportContact and here in the README.)


Quick start: build a receipt and print it #

The fastest way to print a real receipt is the data-first Receipt builder — add your header, line items, totals and footer with actual values (no templates, no {{placeholders}}), then print in one call. It automatically adapts to 58mm/80mm paper, renders non-Latin text as bitmaps, cuts the paper, and flows through the offline queue, retries and licensing.

final printer = ThermalPrinter(profile: PrinterProfile.mm80());
await printer.connectBluetooth(macAddress); // or BLE / USB / Network

final receipt = Receipt(currency: '₹')
  ..title('RAVI TRADERS')
  ..center('123 MG Road, Bengaluru')
  ..center('GSTIN: 29ABCDE1234F1Z5')
  ..feed()
  ..labeled('Invoice', 'INV-1024')
  ..dateTime()
  ..divider()
  ..item('Basmati Rice 5kg', qty: 2, rate: 250) // amount auto = 500
  ..item('Cooking Oil 1L', qty: 1, rate: 180)
  ..item('Delivery', amount: 40)                 // qty/rate optional
  ..divider()
  ..totals(
    subtotal: 720,
    taxes: {'CGST 2.5%': 18, 'SGST 2.5%': 18},
    total: 756,
    tendered: 1000,                              // change auto-computed
  )
  ..feed()
  ..center('Thank you! Visit again.')
  ..qr('https://pay.example/inv/1024');

await printer.printReceipt(receipt, openDrawer: true);

What you can add: title / text / center / right (styled lines), labeled (left–right rows), item (auto qty × rate), totals (subtotal, taxes/discounts, grand total, tendered + auto change), divider, feed, qr, barcode, image (logo), and dateTime. Use Receipt(itemStyle: ReceiptItemStyle.nameAmount) for simple name-and-price bills. The item-name column always flexes to the paper width; widen the numeric columns for big numbers with Receipt(qtyWidth: …, rateWidth: …, amountWidth: …). Preview it on screen with ReceiptPreview(template: receipt.build()).

For fixed, reusable layouts driven by a data map, use the template engine and PrebuiltTemplates (below) instead.


The workflow: connect → template → preview → print #

Connect a printer in one line (drop-in UI) #

The SDK ships a ready-made PrinterConnectPage that handles Bluetooth permissions, turning Bluetooth on, live BLE scanning (with signal strength), paired Bluetooth Classic devices, network auto-discovery, and connecting — with friendly empty states. Just push it:

final printer = ThermalPrinter();

await Navigator.of(context).push(MaterialPageRoute(
  builder: (_) => PrinterConnectPage(
    printer: printer,
    onConnected: (device) => Navigator.pop(context), // now connected & ready
  ),
));

// printer is connected — start printing
await printer.printTemplate(PrebuiltTemplates.retailInvoice(), data: data);

⚠️ Bluetooth won't find anything without permissions. Add them to your app (see Required native permissions) — the widget requests them at runtime, but they must be declared in the manifest/plist too.

Or connect programmatically #

final printer = ThermalPrinter(profile: PrinterProfile.mm80());

// Network auto-discovery — no IP typing
final found = await const NetworkPrinterDiscovery().discover();
await printer.connectDevice(found.first, autoReconnect: true);

// Preview before printing
ReceiptPreview(template: PrebuiltTemplates.retailInvoice(), data: data);

// Print
await printer.printTemplate(PrebuiltTemplates.retailInvoice(), data: data);

Industry quick-starts #

Each prebuilt template documents the exact data keys it expects. Fill them at print time — the layout is reusable.

🛒 Retail (itemized bill with GST + change due) #

await printer.printTemplate(PrebuiltTemplates.retailInvoice(), data: {
  'shopName': 'Ravi Traders', 'shopAddress': 'Jaipur',
  'gstin': '08ABCDE1234F1Z5', 'invoiceNo': 'INV-1042',
  'items': [
    {'name': 'Cotton Shirt', 'qty': '2', 'rate': '499', 'amount': '998'},
    {'name': 'Socks', 'qty': '3', 'rate': '99', 'amount': '297'},
  ],
  'subtotal': '1295.00', 'cgst': '32.37', 'sgst': '32.37',
  'total': '1359.74', 'tendered': '1500.00',
  'receiptUrl': 'https://shop.example/r/1042',
});

🍽️ Restaurant (Kitchen Order Ticket) #

await kitchenPrinter.printTemplate(PrebuiltTemplates.restaurantKot(), data: {
  'tableNo': '7', 'orderNo': 'A-231', 'waiter': 'Suresh',
  'items': [{'qty': '2', 'name': 'Paneer Tikka'}, {'qty': '1', 'name': 'Naan'}],
  'instructions': 'Less spicy, no onion',
});

📦 Logistics (shipping label with COD) #

await printer.printTemplate(PrebuiltTemplates.shippingLabel(), data: {
  'carrier': 'SwiftShip', 'awb': '7789123456',
  'senderName': 'Ravi Traders', 'senderAddr1': 'MI Road', 'senderAddr2': 'Jaipur',
  'senderPhone': '9876543210',
  'receiverName': 'Anita Verma', 'receiverAddr1': 'Sector 21',
  'receiverAddr2': 'Gurugram', 'receiverPhone': '9812345678',
  'weight': '1.2 kg', 'cod': '2723.70',
  'trackingUrl': 'https://swiftship.example/t/7789123456',
});

💊 Pharmacy (dosage + expiry warning) #

await printer.printTemplate(PrebuiltTemplates.pharmacyLabel(), data: {
  'pharmacyName': 'City Care Pharmacy', 'patientName': 'Mohit Sharma',
  'rxNo': 'RX-5521', 'drugName': 'Amoxicillin 500mg',
  'dosage': '1 capsule, 3 times a day after meals',
  'quantity': '21 capsules', 'batch': 'B22F9', 'expiry': '11/2027',
});

Also available: parkingToken, bankMiniStatement, warehouseProductLabel, queueToken.

Build your own template #

final t = TemplateBuilder('My Receipt')
    .text('{{shopName}}', align: 1, bold: true, fontHeight: 2)
    .divider()
    .table(const [
      TableColumn('Item', key: 'name'),
      TableColumn('Amt', width: 9, align: 2, key: 'amount'),
    ], rowsKey: 'items')
    .taxBreakdown(totalValue: '{{total}}', tenderedValue: '{{cash}}',
        currencySymbol: '₹')
    .qrCode('{{url}}')
    .build();

Templates serialize to JSON (t.toJsonString() / PrintTemplate.fromJsonString) so you can design once and store/ship them.


Connectivity features #

// Multiple named printers
final mgr = PrinterManager();
await mgr.add('receipt', (p) => p.connectNetwork('192.168.1.50'));
await mgr.add('kitchen', (p) => p.connectBluetooth('AA:BB:CC:DD:EE:FF'));
await mgr.printTemplateOn('kitchen', kot, data: order);

// Real-time status
final s = await printer.queryStatus();
if (s.paperOut) { /* prompt to add paper */ }

// Offline queue — nothing lost if the link drops mid-shift
final queue = OfflinePrintQueue();
printer.useOfflineQueue(queue); // auto-flushes on reconnect

// Typed errors
try { await printer.printTemplate(t, data: d); }
on PaperOutException { /* ... */ }
on PrinterOfflineException { /* ... */ }

Platform support #

Platform Bluetooth Classic Bluetooth LE USB Network
Android opt-in³
iOS ⚠️ MFi only¹
Windows opt-in³
Linux opt-in³
macOS opt-in³

The public API is identical across platforms; an unsupported transport throws UnsupportedTransportException.

¹ iOS Bluetooth requires MFi-certified printers via the External Accessory framework; use Network for non-MFi printers on iOS. ³ USB is not bundled: the common usb_serial plugin is unmaintained and breaks modern Android builds (AGP 8/9). The connectUSB() API is preserved but inert until you wire a maintained USB plugin (e.g. quick_usb) into your app. Most USB thermal printers on desktop are reachable via the OS print queue or a virtual COM port instead.

Required native permissions #

Android — add to android/app/src/main/AndroidManifest.xml, above <application> (without these, Bluetooth scans return nothing):

<!-- Android 12+ -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
<!-- 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"/>
<!-- BLE scanning needs location on older Android -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

iOS — add to ios/Runner/Info.plist:

<key>NSBluetoothAlwaysUsageDescription</key>
<string>Connect to Bluetooth thermal printers.</string>

PrinterConnectPage (and PrinterScanner.ensureBluetoothPermissions()) request these at runtime; the manifest/plist entries above are still required.


Example app #

The example/ app is a complete workflow: a printer picker (with auto-discovery and live status), a gallery of the industry templates, a live receipt preview, and one-tap printing — with licensing handled internally and a free-mode quota banner.

cd example
flutter create .   # generate platform folders the first time
flutter run

License #

See LICENSE. This SDK includes an optional online activation backend; the owner-only setup (Firebase + issuing keys) lives in OWNER_SETUP_PRIVATE.md (git-ignored). Publishing steps are in PUBLISHING.md.

3
likes
140
points
11
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Universal Thermal Printer SDK for Flutter — Bluetooth & Network (WiFi/LAN) ESC/POS thermal printer support with text, QR, barcode, images, and built-in remote license activation.

Homepage

License

unknown (license)

Dependencies

crypto, flutter, flutter_blue_plus, http, image, permission_handler, print_bluetooth_thermal, shared_preferences

More

Packages that depend on universal_thermal_printer