thermal_printer_flutter 2.0.0+3
thermal_printer_flutter: ^2.0.0+3 copied to clipboard
A Flutter plugin for thermal printing supporting Bluetooth, USB, and Network printers across Android, iOS, macOS, Windows, Linux, and Web platforms. Easy to use and integrates with esc_pos_utils for g [...]
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 | ❌ | ✅ (BLE/RFCOMM) | ✅ |
| iOS | ❌ | ✅ (BLE) | ✅ |
| macOS | ✅ | 🚧 | ✅ |
| Windows | ✅ | ✅ (RFCOMM) | ✅ |
| Linux | ❌ | ❌ | ✅ |
| Web | ✅ | ✅ (BLE) | ❌ |
¹ Web uses WebUSB and Web Bluetooth — Chromium browsers only (Chrome/Edge/Opera; not Safari/Firefox), over HTTPS or localhost. Network (raw TCP 9100) is not possible in a browser. See Web below for the important caveats.
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 #
Web printing works over WebUSB and Web Bluetooth (BLE). There is no native setup, but the browser imposes hard constraints — read these before shipping.
TL;DR: BLE works reliably on the web (Chromium). USB also works on Windows / Linux / ChromeOS, but on macOS a USB printer is held by the system print driver and needs a cable replug to authorize — so on macOS, prefer BLE. See the macOS + USB printers note below.
Browser & context
- Chromium only (Chrome / Edge / Opera). Safari and Firefox do not implement WebUSB/Web Bluetooth. Detect at runtime with
isWebUsbSupported()/isWebBluetoothSupported(). - Secure context required: HTTPS, or
http://localhostfor development.
What the browser does not allow
- No silent discovery / no scanning. A page can never enumerate devices on its own. The only flow is: user gesture (button click) → the browser's own device chooser → user picks the device. After that the grant is remembered and the device reconnects without the chooser (
getDevices). - No raw network (TCP 9100). Browsers can't open raw sockets, so
PrinterType.networkis not available on web (only USB and BLE). A server/WebSocket bridge would be required. - Bluetooth is BLE only — classic/RFCOMM is not exposed to the web. The printer must advertise a known GATT write service; the common ESC/POS UUIDs are in
printerServiceUuids(lib/src/web/web_bluetooth.dart). If your model uses a different service it won't be found — open an issue/PR with the UUID.
Permission is per-origin — pin the dev port
WebUSB/Web Bluetooth permission is scoped to the origin = scheme + host + port. flutter run -d chrome picks a random port every run, so the grant is lost each time and getDevices() comes back empty. Pin the port so the grant (and silent reconnect) persists:
flutter run -d chrome --web-port=8080
⚠️ macOS + USB printers — known limitation
On macOS, a class-compliant USB printer (interface class 0x07) is claimed by the system print driver as soon as it enumerates. Chrome cannot offer a device whose interface is held by a kernel driver, so the WebUSB chooser comes up empty. This is a macOS/OS-level constraint — there is no code-side fix (unlike Windows, which can override the driver with a WinUSB INF, or Linux with a udev rule; macOS has no clean equivalent).
Confirmed behavior and workarounds:
- The device is always visible in
chrome://usb-internals, but not in the WebUSB chooser, because the OS holds it. - Physically unplugging and reconnecting the USB cable frees the device for a brief window — click Authorize right after the replug and it works. The
connectevent (seeonWebUsbConnectionChange) fires on that replug, so once a printer has been authorized it then reconnects automatically each time it's plugged in. - For reliable web printing on macOS, use Bluetooth (BLE) — it doesn't compete with the print driver.
WebUSB works normally on Windows, Linux and ChromeOS. The replug dance is specific to macOS class-0x07 printers.
See Web (USB & Bluetooth) under Usage for the API.
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
On Web,
getPrintersreturns only devices the user already authorized (it never opens a chooser). UserequestPrinterto authorize a new one. See Web (USB & Bluetooth).
Web (USB & Bluetooth) #
On the web, the browser must prompt the user before a device is accessible. The pattern is authorize once, then reconnect silently:
// 1. Feature-detect (Chromium only, HTTPS/localhost).
if (await printer.isWebUsbSupported()) {
// 2. Authorize. Smart: reuses an already-granted device if present,
// and only opens the browser chooser when needed. MUST be called
// from a user gesture (e.g. a button's onPressed).
final target = await printer.requestPrinter(printerType: PrinterType.usb);
if (target != null) {
await printer.printBytes(bytes: bytes, printer: target);
}
}
// Web Bluetooth (BLE) is the same, with PrinterType.bluetooth:
if (await printer.isWebBluetoothSupported()) {
final ble = await printer.requestPrinter(printerType: PrinterType.bluetooth);
}
Auto-reconnect already-authorized USB printers when the cable is plugged in (no chooser):
// Re-resolve granted devices on every USB connect/disconnect event.
final sub = printer.onWebUsbConnectionChange.listen((_) async {
final usb = await printer.getPrinters(printerType: PrinterType.usb);
// usb now contains the reconnected printer(s).
});
// ...
await sub.cancel(); // when done
requestPrinter,isWebUsbSupported,isWebBluetoothSupportedandonWebUsbConnectionChangeare no-ops/empty on non-web platforms, so the same code is safe everywhere.
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.
Print #
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. - Web: Chromium only (HTTPS/localhost); no network transport; BLE only (no classic); WebUSB permission is per-origin including the port (pin
--web-portin dev); on macOS the system print driver competes for USB printers (prefer BLE). See Web setup.
License #
MIT — see LICENSE.