zebra_printer 🦓
A Flutter plugin for Zebra Label Printers — built on the official Zebra Link-OS™ SDK.
Unlike generic Bluetooth plugins, this package communicates with Zebra printers the right way: through the SDK that Zebra itself provides. That means reliable discovery, real printer status (paper out, head open, ribbon out), and optimized printing for high-volume label workflows.
Android only — iOS is not supported by the Zebra Link-OS SDK for Bluetooth printing.
What can you do with this package?
| Task | How |
|---|---|
| Find nearby Zebra printers | startDiscovery() |
| Print a ZPL label | safePrint(address, zpl) |
| Print 100 labels at once | safeBatchPrint(address, zplList) |
| Check if printer is ready | getZebraPrinterStatus(address) |
| Get model, serial, firmware | getPrinterInfo(address) |
| Upload a custom font | uploadFont(address, assetPath) |
| Pair / unpair devices | pairDevice() / unpairPrinter() |
| Track print progress (0–100%) | onPrintProgress callback |
Installation
dependencies:
zebra_printer: ^0.4.3
Android Permissions
Add these to your android/app/src/main/AndroidManifest.xml, before the <application> tag:
<!-- Required for Bluetooth on Android ≤ 11 -->
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<!-- Required for Bluetooth on Android 12+ -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
<!-- Required for Bluetooth discovery -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- Required for Network discovery -->
<uses-permission android:name="android.permission.INTERNET" />
Quick Start
1 — Create the manager
import 'package:zebra_printer/zebra_printer.dart';
final printer = PrinterManager(); // singleton
2 — Discover printers
// Set callbacks before starting discovery
printer.onPrinterFound = (p) {
print('Found: ${p.friendlyName} (${p.address})');
};
printer.onDiscoveryFinished = (printers) {
print('Discovery done. ${printers.length} printer(s) found.');
};
// Start Bluetooth discovery
await printer.startDiscovery(type: DiscoveryType.bluetooth);
3 — Print a label
// safePrint handles locking, retries, and progress tracking automatically
await printer.safePrint(
'AC:3F:A4:XX:XX:XX',
'^XA^FO50,50^A0N,50,50^FDHello Zebra!^FS^XZ',
);
4 — Track progress
printer.onPrintProgress = (progress) {
print('${progress.percentage}% — ${progress.state}');
if (progress.hasError) {
print('Error: ${progress.errorMessage}');
}
if (progress.hasPrinterIssue) {
print('Warning: ${progress.printerWarning}');
}
};
5 — Batch print (multiple labels, one connection)
final labels = List.generate(
10,
(i) => '^XA^FO50,50^A0N,50,50^FDLabel ${i + 1}^FS^XZ',
);
await printer.safeBatchPrint('AC:3F:A4:XX:XX:XX', labels);
Printer Status
Before printing, you can check if the printer is ready:
final status = await printer.getZebraPrinterStatus('AC:3F:A4:XX:XX:XX');
if (status.isReadyToPrint) {
print('Ready to print!');
} else {
print(status.statusMessage); // e.g. "🔴 Paper out!"
}
// Individual status flags
status.isPaperOut // paper empty
status.isHeadOpen // lid is open
status.isRibbonOut // ribbon empty (thermal transfer)
status.isHeadCold // head warming up
status.isHeadTooHot // head cooling down
status.isReceiveBufferFull // printer is busy
status.labelsRemainingInBatch // labels left in current batch
// Helper getters
status.canPrint // all-clear: ready & no errors
status.isBusy // currently printing or has queued jobs
status.hasCriticalError // paper out / head open / ribbon out
You can also poll until the printer is ready:
final status = await printer.waitForPrinterReady(
'AC:3F:A4:XX:XX:XX',
timeout: Duration(seconds: 15),
);
Printer Info
final info = await printer.getPrinterInfo('AC:3F:A4:XX:XX:XX');
print(info.model); // e.g. "ZD421"
print(info.serialNumber); // e.g. "D3J185400123"
print(info.firmware); // e.g. "V84.20.11Z"
print(info.language); // PrinterLanguage.zpl
Font Upload
Upload a custom TTF font to the printer's flash memory:
// From assets (add font file to pubspec.yaml assets first)
await printer.uploadFont(
'AC:3F:A4:XX:XX:XX',
'assets/fonts/arial.zpl', // ZPL-wrapped TTF or raw TTF
);
// From raw bytes
await printer.uploadFontData('AC:3F:A4:XX:XX:XX', fontBytes);
Then use the font in ZPL:
^XA^A@N,50,50,E:ARIAL.TTF^FO50,50^FDCustom Font^FS^XZ
Pairing Management
// List currently paired Zebra printers
final paired = await printer.getPairedPrinters();
// Remove a pairing
await printer.unpairPrinter('AC:3F:A4:XX:XX:XX');
Architecture
This package provides two managers:
zebra_printer
├── PrinterManager ← Use this for everything Zebra-related
│ ├── Discovery (Zebra SDK: BluetoothDiscoverer)
│ ├── Printing (Zebra SDK: connection.write + status polling)
│ ├── Status (Zebra SDK: printer.getCurrentStatus())
│ ├── Info (Zebra SDK: SGD.GET commands)
│ └── Font Upload (Zebra SDK: ZebraPrinterLinkOs.downloadTtfFont)
│
└── BluetoothManager ← Generic Bluetooth operations only
├── Device scanning (Android Bluetooth API)
├── Pairing (Android Bluetooth API)
└── Raw connection (Android BluetoothSocket)
PrinterManager uses the official Zebra Link-OS™ SDK for all printer operations.
BluetoothManager uses the standard Android Bluetooth API for generic device management.
Lifecycle
Both managers are singletons. Call dispose() when you no longer need them (e.g. in your widget's dispose()), and reinitialize() if you need to reuse them later:
@override
void dispose() {
printer.dispose();
super.dispose();
}
// If you need to reuse:
printer.reinitialize();
Calibration & Test Print
// Fix misaligned labels
await printer.calibratePrinter(address, CalibrationType.gap); // gap labels
await printer.calibratePrinter(address, CalibrationType.bar); // black-mark labels
// Print a built-in test label
await printer.printTestLabel(address);
Logging
// Enable logs (useful during development)
await printer.enableLogging();
// Disable logs (recommended for production)
await printer.disableLogging();
Troubleshooting
| Problem | Solution |
|---|---|
| Printer not found | Make sure Bluetooth is ON, Location is enabled, and permissions are granted |
| Discovery finds nothing | The printer might already be paired — tap "Paired" to list bonded devices |
| Connection fails | Zebra Bluetooth printers support only one active connection. Disconnect other devices first |
| Labels are misaligned | Run calibratePrinter(address, CalibrationType.gap) |
| ZPL not printing correctly | Preview your ZPL at Labelary Viewer |
safePrint throws PrintLockedException |
A previous print is still in progress. Wait or call forceReleasePrintLock() |
ZPL Resources
- Labelary Online Viewer — test ZPL in the browser
- Zebra ZPL Reference — complete ZPL command reference
- Always wrap ZPL with
^XA…^XZ
Example App
A full working example with UI, discovery, printing, batch printing, status checking, and printer info is available in the example/ folder.
License
MIT License — Copyright © 2025 Samed Demir
Powered by Zebra Link-OS™ SDK