Flutter ESC/POS Network Universal
A high-performance, truly cross-platform Flutter library for printing to ESC/POS thermal network printers (Wi‑Fi / Ethernet) over TCP — including Flutter Web.
One API. Identical code on mobile, desktop, and the browser. On native platforms it opens a direct TCP socket; on the web it routes through the companion Local TCP browser extension, which the framework detects and uses automatically.
This package is a forked and substantially extended version of
flutter_esc_pos_network, re-architected for universal (IO and Web) support.
✨ Features
- Universal API — one code path for Android, iOS, Windows, macOS, Linux, and Web.
- Widget printing — render any Flutter
Widgetto the printer withprintWidget(logos, QR codes, rich layouts, mixed scripts). - Raw ESC/POS — send pre-built command bytes with
printTicket; the fullflutter_esc_pos_utilsAPI (Generator,PaperSize,CapabilityProfile, …) is re-exported for you. - Off-thread image processing — on mobile/desktop, bitmap rasterization runs in a background isolate (
compute) to keep the UI smooth. - Seamless raster output — the receipt bitmap is emitted as a single continuous ESC/POS image, avoiding the white-line seam artifacts of chunked rendering.
- Robust bitmap pipeline — captured widgets are normalized to 8‑bit and clamped to the print‑head width, so output is consistent across every renderer and device.
- Multiple paper widths — 58 mm, 72 mm, and 80 mm.
📋 Platform support
| Platform | Transport | Extra setup required |
|---|---|---|
| Android · iOS · Windows · macOS · Linux | Direct TCP socket (dart:io) |
None |
| Web | Local TCP browser extension + one‑click native host | Yes — see Web setup |
Browsers cannot open raw TCP sockets, and thermal printers speak raw ESC/POS — so the web target relies on the Local TCP bridge to reach the printer. Native platforms have no such restriction and need nothing extra.
🚀 Installation
dependencies:
flutter_esc_pos_network_universal: <latest-version>
import 'package:flutter_esc_pos_network_universal/flutter_esc_pos_network_universal.dart';
⚡ Quick start
Print raw ESC/POS bytes
final printer = PrinterNetworkManager(
'192.168.1.100',
port: 9100,
paperSize: ThermalPosPrinterPageSize.size80mm,
// On web, allow extra time so the bridge can wake a sleeping Wi‑Fi printer.
timeout: const Duration(seconds: 30),
);
final profile = await CapabilityProfile.load();
final generator = Generator(PaperSize.mm80, profile);
final bytes = <int>[
...generator.text('ALGORAMMING CAFE',
styles: const PosStyles(align: PosAlign.center, bold: true)),
...generator.text('Espresso .......... \$3.00'),
...generator.feed(2),
...generator.cut(),
];
// Connects, prints, and disconnects in one call (isDisconnect defaults to true).
final result = await printer.printTicket(bytes);
if (result != PosPrintResult.success) {
debugPrint('Print failed: ${result.msg}');
}
Need to send several tickets on one connection? Pass isDisconnect: false and call printer.disconnect() yourself when done:
await printer.connect();
await printer.printTicket(ticket1, isDisconnect: false);
await printer.printTicket(ticket2, isDisconnect: false);
await printer.disconnect();
Print a Flutter widget
await printer.printWidget(
context,
child: const MyReceiptWidget(), // any standard Flutter widget
);
The widget is rasterized and sent as a single ESC/POS image — ideal for logos, QR codes, and layouts that text commands can't express.
🌐 Web setup
On the web, the end user installs the Local TCP extension once; it ships a tiny native messaging host that owns the actual TCP socket.
- Install the extension from the Chrome Web Store (works on Chrome, Edge, Chromium, and Brave).
- Open the extension popup → “Download One‑Click Installer.” It auto‑detects the OS and installs the native host:
- 🍎 macOS — open the
.pkgand follow the prompts. - 🪟 Windows — run the
.exe(per‑user, no admin rights). - 🐧 Linux — run the
.runinstaller.
- 🍎 macOS — open the
- Restart the browser. The popup shows Bridge Linked when ready.
No files are copied by hand and no terminal is needed. Source, installers, and a setup video live in the extension repo: github.com/algonize/local_tcp · Setup video.
Detect the bridge before printing (recommended)
Because the web target depends on the extension being installed and linked, check availability first and guide the user if it's missing. A complete, copy‑pasteable Riverpod implementation — distinguishing not installed, bridge offline, and ready — is in the example app:
➡️ example/lib/provider/local_tcp_extension_provider.dart
🏗️ How it works
The public PrinterNetworkManager is a thin facade that selects a platform implementation at compile time via conditional import:
PrinterNetworkManager (public API)
│
├── IO → dart:io Socket.connect(host, 9100) # mobile / desktop
│ └─ bitmap rasterized in a background isolate
│
└── Web → window.postMessage ⇄ Local TCP extension # browser
└─ extension's native host opens the socket
On the web, requests are serialized as { source: 'localtcp_req', messageId, action, host, port, data } and matched to localtcp_res replies by messageId. The browser, extension, and native host together relay raw ESC/POS bytes to the printer and stream the result back.
📚 API reference
PrinterNetworkManager
| Member | Description |
|---|---|
PrinterNetworkManager(host, {port = 9100, timeout = 5s, paperSize = size80mm, profile}) |
Create a manager for a printer. |
Future<PosPrintResult> connect({timeout}) |
Connect to the printer. |
Future<PosPrintResult> disconnect({timeout}) |
Disconnect. |
Future<PosPrintResult> printTicket(List<int> data, {isDisconnect = true}) |
Send raw ESC/POS bytes; auto‑connects if needed. |
Future<PosPrintResult> printWidget(BuildContext context, {required Widget child, isDisconnect = true}) |
Rasterize and print a widget. |
bool get isConnected · ThermalPosPrinterPageSize get paperSize · CapabilityProfile? get profile |
State getters. |
Enums
PosPrintResult—success,timeout,connectionRefused,socketError,ticketEmpty,printInProgress,disconnectError, … Each carries a human‑readable.msg.ThermalPosPrinterPageSize—size58mm,size72mm,size80mm.
Paper sizes
| Size | Render width | Underlying profile |
|---|---|---|
size58mm |
384 px | 58 mm |
size72mm |
512 px | 80 mm |
size80mm |
576 px | 80 mm |
⚠️ Notes & tips
- Web image processing runs on the main thread (isolates aren't used on web). For very large receipts, prefer building bytes with
Generatorand callingprintTicketoverprintWidget. - On web, set a generous
timeout(e.g. 30 s) so the bridge can wake a deep‑sleeping Wi‑Fi printer. - The standard network‑printer port is
9100.
🤝 Contributing
Bug reports, feature requests, and PRs are welcome — please open an issue or pull request on the GitHub repository.
⚖️ License
MIT © Algoramming Systems Ltd. — see LICENSE.