thermal_printer_flutter
Flutter plugin for ESC/POS thermal printing over USB, Bluetooth (BLE) and Network (TCP/IP) on Android, iOS, macOS, Windows, Linux and Web.
Platform support
| Platform | USB | Bluetooth | Network |
|---|---|---|---|
| Android | ❌ | ✅ | ✅ |
| iOS | ❌ | ✅ | ✅ |
| macOS | ✅ | 🚧 | ✅ |
| Windows | ✅ | ✅ | ✅ |
| Linux | ❌ | ❌ | ✅ |
| Web | ❌ | ❌ | 🚧 |
Permissions & setup per platform
Android
Add to android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
Android 12+ also needs:
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
iOS
Add to ios/Runner/Info.plist:
<key>NSBluetoothAlwaysUsageDescription</key>
<string>We need Bluetooth access to connect to thermal printers</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>We need Bluetooth access to connect to thermal printers</string>
iOS 13+ also needs:
<key>NSBluetoothAlwaysAndWhenInUseUsageDescription</key>
<string>We need Bluetooth access to connect to thermal printers</string>
macOS
Add the Bluetooth keys to macos/Runner/Info.plist:
<key>NSBluetoothAlwaysUsageDescription</key>
<string>We need Bluetooth access to connect to thermal printers</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>We need Bluetooth access to connect to thermal printers</string>
Add the entitlements to both macos/Runner/DebugProfile.entitlements and macos/Runner/Release.entitlements:
<key>com.apple.security.device.bluetooth</key>
<true/>
<key>com.apple.security.print</key>
<true/>
USB on macOS goes through the system print queue (CUPS), not raw USB. Add the printer in System Settings → Printers & Scanners first (the Generic driver works for most ESC/POS printers).
getPrinters(printerType: PrinterType.usb)then lists the installed queues.
Windows
No manifest permissions required. Install the USB printer driver so it shows up in the Windows print spooler. (Bluetooth is not supported on Windows.)
Linux
Network only — make sure the firewall allows the printer port (default 9100).
Web
Experimental. Network printing requires a server/proxy that accepts the connection (raw TCP sockets aren't available in the browser).
Usage
final printer = ThermalPrinterFlutter();
List printers
final usb = await printer.getPrinters(printerType: PrinterType.usb); // Windows, macOS
final bluetooth = await printer.getPrinters(printerType: PrinterType.bluetooth); // Android, iOS, macOS
Discover network printers
final found = await printer.discoverNetworkPrinters(
onProgress: (p) => print(p),
requireConfirmation: true, // optional: probe port 9100 (ESC/POS) to drop false positives
);
Or add one manually:
final p = Printer(type: PrinterType.network, name: 'Kitchen', ip: '192.168.1.50', port: '9100');
Connect (Bluetooth / Network)
await printer.connect(printer: target);
await printer.disconnect(printer: target);
USB printers are connectionless — no
connect()needed.
final generator = Generator(PaperSize.mm80, await CapabilityProfile.load());
final bytes = <int>[]
..addAll(generator.text('Hello', styles: const PosStyles(align: PosAlign.center, bold: true)))
..addAll(generator.feed(2))
..addAll(generator.cut());
await printer.printBytes(bytes: bytes, printer: target);
// Multiple copies in a single job (do not call printBytes in a loop):
await printer.printBytes(bytes: bytes, printer: target, copies: 2);
Print a widget / image
final image = await printer.screenShotWidget(
context,
widget: MyReceipt(),
width: 576, // 80 mm @ 203 dpi (use 384 for 58 mm)
pixelRatio: 4.0,
dither: true, // Floyd–Steinberg (default) — best for logos/photos
);
final bytes = <int>[]
..addAll(generator.imageRaster(image))
..addAll(generator.cut());
await printer.printBytes(bytes: bytes, printer: target);
Printer status (USB)
final status = await printer.getPrinterStatus(printer: target);
print('${status.description} (paperOut=${status.isPaperOut}, offline=${status.isOffline})');
Release resources
await printer.dispose(); // closes pooled network sockets
Notes & limitations
getPrinterStatusreturns real data only for USB printers on Windows (spooler) and macOS (CUPS); otherwisePrinterStatus.unknown.isConnectedis alwaystruefor USB (connectionless) — usegetPrinterStatusfor real health.- Bluetooth manages a single active connection;
disconnect()closes the active one regardless of thePrinterpassed. - Network discovery flags any host with an open port (9100/515/631) as a candidate — use
requireConfirmation: trueto reduce false positives.
License
MIT — see LICENSE.