Flutter ZPL Generator
flutter_zpl_generator is a Flutter and Dart package that turns a declarative, widget-like API into ZPL (Zebra Programming Language) code for Zebra thermal label printers. Use it to build shipping labels, product labels, barcodes, QR codes and receipts in Dart, preview them inside your app, and get a ready-to-print ^XAβ¦^XZ string.
It adds TTF font conversion, automatic image-to-ZPL conversion with dithering and compression, and a 12-column grid layout engine so you don't hand-place every ^FO coordinate.
π¨οΈ Print what you build:
flutter_zpl_printersends these labels to Zebra printers over Bluetooth LE, Wi-Fi, or USB, and includes this package. See Print to a Zebra printer.
π Table of Contents
- Component Demos & Previews
- What Makes This Package Special
- Quick Start
- The Layout Engine (Visuals)
- Images & Fonts
- The Data Engine
- Hardware & Enterprise Control
- Developer Experience & Examples
- FAQ
- Related Projects
πΈ Component Demos & Previews
The library boasts an unparalleled set of components, beautifully simulated inside Flutter using the ZplPreview widget via the Labelary REST API:
| Text & Fonts | Barcodes & Data Matrix |
|---|---|
![]() |
![]() |
| Graphics & Shapes | Responsive 12-Unit Grid Layout |
![]() |
![]() |
| Image Dithering Algorithms | |
![]() |
π What Makes This Package Special
π Robust Native Layout Architecture
ZplGridRow&ZplGridCol: Build complex receipt layouts utilizing a proportional 12-unit grid container with offset spacing capabilities instead of brittle raw^FO(Field Origin) numbers.- Natural Configuration Flow:
ZplConfigurationpropagates correctly down to children natively, accurately calculatingZplAlignment.rightorZplAlignment.centerbased onprintWidth.
π€ TTF to ZPL Font Conversion (First in Flutter!)
- Convert any TrueType font to ZPL format and upload custom fonts directly to your Zebra printer's memory.
- Use your brand fonts in labels for perfect consistency!
- No more limitations to basic printer fonts.
π― Unrivaled ZPL Component Coverage
- Included shapes:
ZplBox,ZplGraphicCircle(^GC),ZplGraphicEllipse(^GE),ZplGraphicDiagonalLine(^GD). - Reverse Print Support (^FR): Easily achieve stunning white-on-black UI elements utilizing
reversePrint: true. - Native Support for DataMatrix (^BX), EAN-13 (^BE), UPC-A (^BU), Code 128 (^BC), Code 39 (^B3), and QR Codes (^BQ).
ZplRawSupport: An escape hatch that lets you cleanly inject highly specific/legacy raw strings (e.g.,^MDdarkness or RFID triggers).
πΌοΈ Live Flutter Preview
- The
ZplPreviewwidget hooks up directly to Labelary endpoints recursively respecting thegeneratorstate for immediate real-time feedback visually in Flutter while you code. - Alternatively, you can run the
ZplNativePreviewwidget which operates completely 100% offline leveragingCustomPainterto natively reconstruct elements!
How accurate is it? The native preview is calibrated against Labelary renders (see
test/native_preview_fidelity_harness_test.dart). Since v2.1: 1D barcodes are drawn from the real module count (Code 128 with Zebra's subset switching, Code 39 at the^BYratio), QR and Data Matrix at the exact version and size, and text with a bundled condensed bold face (Archivo Narrow, SIL OFL) sized to Zebra's font 0 metrics. Boxes, Data Matrix and alphanumeric QR are pixel-identical; 1D barcodes overlap 90β95 %; text boxes land within a few dots but glyph shapes still differ from Zebra's CG Triumvirate, and the QR mask pattern can differ (same size and content).
π Quick Start
Basic Label Generation
import 'package:flutter_zpl_generator/flutter_zpl_generator.dart';
// Create ZPL commands and pass the configuration natively
final generator = ZplGenerator(
config: const ZplConfiguration(
printWidth: 406, // 2 inches at 203 DPI
labelLength: 203, // 1 inch at 203 DPI
printDensity: ZplPrintDensity.d8, // 8 dpmm / 203 DPI
),
commands: [
// Simple text handling
ZplText(x: 20, y: 20, text: 'Hello World!'),
// Barcode Support
ZplBarcode(
x: 20, y: 60,
height: 50,
data: '12345',
type: ZplBarcodeType.code128,
printInterpretationLine: true,
),
],
);
// Generate ZPL string
// (Async required if parsing external images or TTF fonts!)
final zplString = await generator.build();
print(zplString);
// Output: ^XA^LL203^PR8^JMB^FO20,20^A0N,,...^XZ
π¨οΈ Print to a Zebra printer
This package builds ZPL; it doesn't talk to printers. To send labels to a Zebra printer, use the companion
package flutter_zpl_printer. It discovers and connects over
Bluetooth LE, Wi-Fi/TCP, and USB, and it includes flutter_zpl_generator, so one dependency
and one import cover both.
dependencies:
flutter_zpl_printer: ^0.2.0 # includes flutter_zpl_generator
import 'package:flutter_zpl_printer/flutter_zpl_printer.dart'; // also exports this package
final printer = await ZebraPrinter.connect(TcpConnection.zpl('192.168.1.50')); // or BleConnection, UsbConnection
await printer.printLabel(generator); // the ZplGenerator from above
await printer.disconnect();
printLabel builds and sends the label. You can also send any ZPL string with printer.printZpl(zpl),
for example the output of a ZplTemplate. See the
flutter_zpl_printer README for discovery, permissions,
status checks, and platform support. The example app in this repository has a
Send to printer button on every demo.
ποΈ The Layout Engine (Visuals)
A massive advantage over writing raw strings is abstracting raw X/Y origins via container-based bounds checks.
The 12-Unit Grid (ZplGridRow)
Create responsive horizontal layouts easily without manually calculating X-offsets.
ZplGridRow(
y: 355,
children: [
ZplGridCol(
width: 6, // 50% width bounds
offset: 0,
child: ZplText(text: 'LEFT COLUMN', alignment: ZplAlignment.left),
),
ZplGridCol(
width: 6, // 50% width bounds
child: ZplText(text: 'RIGHT COLUMN', alignment: ZplAlignment.right),
),
],
)
Advanced Tabular Data (ZplTable)
ZplTable(
y: 780,
columnWidths: [5, 2, 2, 3], // The 12-unit layout
borderThickness: 2,
cellPadding: 6,
headers: [
ZplTableHeader('Product', alignment: ZplAlignment.left),
ZplTableHeader('Qty', alignment: ZplAlignment.center),
ZplTableHeader('Price', alignment: ZplAlignment.right),
],
data: [
['Widget Pro', '2', '\$15.00'],
['Gadget XL', '1', '\$42.50'],
],
)
Graphic Shapes & Escapes
// Shapes
ZplGraphicCircle(x: 20, y: 310, diameter: 100, borderThickness: 2)
ZplGraphicEllipse(x: 260, y: 485, width: 80, height: 100, borderThickness: 2)
// Diagonal Lines
ZplGraphicDiagonalLine(
x: 20, y: 665,
width: 150, height: 120,
borderThickness: 3, orientation: 'R', // Or 'L'
)
// Inverted Reverse Print (White Text, Black box)
ZplBox(x: 20, y: 620, width: 772, height: 50, borderThickness: 50)
ZplText(
x: 20, y: 630,
text: 'WHITE ON BLACK',
reversePrint: true, // Output ^FR
)
// The Raw Escape Hatch
ZplRaw(command: '^FO20,880^A0N,24,20^FDInject anything directly!^FS')
Barcode Symbologies (ZplBarcode)
Thirteen symbologies via type:; the offline ZplNativePreview renders all of them.
| 1D | 2D |
|---|---|
code128 (^BC), gs1_128 (^BC mode D, (AI) data), code39 (^B3), code93 (^BA), interleaved2of5 (^B2), ean13 (^BE), ean8 (^B8), upcA (^BU), upcE (^B9) |
qrCode (^BQ), dataMatrix (^BX), pdf417 (^B7), aztec (^BO) |
ZplBarcode(
data: 'https://example.com',
type: ZplBarcodeType.qrCode,
height: 0, // QR/Aztec size by magnification
magnification: 5,
qrErrorCorrection: ZplQrErrorCorrection.high, // L / M / Q / H
),
ZplBarcode(
data: '(01)09501101530003(17)261231',
type: ZplBarcodeType.gs1_128,
height: 80,
),
ZplBarcode(
data: 'Stacked payload',
type: ZplBarcodeType.pdf417,
height: 8, // row height
pdf417SecurityLevel: 3,
pdf417Columns: 4,
),
Conditional Printing (ZplConditional)
Sometimes you want to show or hide entire layout sections natively based on condition flags (e.g. hasDiscount, showSerialNumber).
Instead of writing messy ternary operators inside Dart lists if (true) myObj,, we've introduced ZplConditional mapping. If condition: false, it safely outputs an empty string, and gracefully returns 0 layout height so surrounding containers like ZplColumn immediately collapse the missing element without rendering any blank gaps!
final generator = ZplGenerator(
commands: [
ZplText(text: 'Product: Widget'),
ZplConditional(
condition: product.hasDiscount, // If false, the below command is skipped AND collapses safely in columns
child: ZplText(text: 'SALE: \${product.discount}% OFF', reversePrint: true),
),
ZplBarcode(data: product.sku, type: ZplBarcodeType.code128),
],
)
π¨ Images & Fonts
Convert Images to ZPL Graphics
Transform any image (PNG, JPEG, GIF) into ZPL graphics that can be embedded directly in your labels:
// Convert to ZPL graphics
final zplGraphics = await LabelaryService.convertImageToGraphic(
imageBytes,
'logo.png',
outputFormat: LabelaryOutputFormat.zpl,
);
Advanced Image Dithering (Pristine Graphics)
v2.0 splits image commands into three explicit shapes. Use ZplImageDownload + ZplImageRecall for the Link-OS-safe flow (~DG before ^XA, ^XG inside). All three dithering algorithms are supported on the ZplImageDownload.
// v2.0 Link-OS-safe: download pre-^XA, recall in-format.
commands: [
ZplImageDownload(
image: bytes,
graphicName: 'LOGO_FS',
ditheringAlgorithm: ZplDitheringAlgorithm.floydSteinberg,
),
const ZplImageRecall(x: 20, y: 20, graphicName: 'LOGO_FS'),
ZplImageDownload(
image: bytes,
graphicName: 'LOGO_ATK',
ditheringAlgorithm: ZplDitheringAlgorithm.atkinson,
),
const ZplImageRecall(x: 20, y: 150, graphicName: 'LOGO_ATK'),
ZplImageDownload(
image: bytes,
graphicName: 'LOGO_THR',
ditheringAlgorithm: ZplDitheringAlgorithm.threshold,
),
const ZplImageRecall(x: 20, y: 280, graphicName: 'LOGO_THR'),
]
Use autoLabelLengthFromFirstImage: true on ZplGenerator to auto-emit ^LL equal to the first download's rendered height.
Image Compression (ACS, B64, Z64)
Both ZplImageDownload (~DG) and ZplImageInline (^GFA) accept a compression: mode:
| Mode | Body | Typical size vs raw hex | Firmware |
|---|---|---|---|
none |
ASCII hex | 100 % | all |
acs |
run-length hex | 10β40 % | all |
b64 |
:B64: base64 + CRC-16 |
~67 % | B64/Z64-capable (all Link-OS) |
z64 |
:Z64: zlib + base64 + CRC-16 |
5β30 % | B64/Z64-capable (all Link-OS) |
commands: [
ZplImageDownload(
image: logoBytes,
graphicName: 'LOGO',
compression: ZplImageCompression.z64, // smallest wire size
),
const ZplImageRecall(x: 20, y: 20, graphicName: 'LOGO'),
ZplImageInline(
x: 20, y: 400,
image: highResPhotoBytes, // defaults to ACS
compression: ZplImageCompression.z64,
),
]
Z64 runs on web too (pure-Dart deflate via package:archive). ZplImageInline is not recommended on Link-OS mobile; see doc/mobile-printer-guide.md.
Import Custom Fonts to Your Printer
Upload TrueType fonts to the printer's memory via the control-phase ZplFontUpload. Reference the font on any ZplText via customFont:.
import 'package:flutter_zpl_generator/flutter_zpl_generator.dart';
// Load the TTF once.
final roboto = await ZplFontUpload.fromAsset(
'assets/fonts/Roboto-Regular.ttf',
'R', // single letter identifier
);
final generator = ZplGenerator(
config: const ZplConfiguration(printWidth: 406, labelLength: 609),
commands: [
roboto, // ~DY upload emitted BEFORE ^XA
ZplText(
x: 50, y: 100,
text: 'Custom Font Text!',
customFont: roboto,
fontHeight: 25,
),
],
);
final zpl = await generator.build();
πΎ The Data Engine
Data Binding & Templating Engine
In logistics and production environments, you shouldn't compile identical labels from scratch 10,000 times. ZplTemplate enables you to cache the heavy geometry and image rendering algorithms once, unlocking blistering fast synchronous label generations.
- Design the layout using
{{variable}}placements in standard commands. - Init the template once globally.
- Bind synchronous data maps aggressively in a loop.
// 1. Initial Setup
final template = ZplTemplate(
ZplGenerator(
config: const ZplConfiguration(printWidth: 406, labelLength: 203),
commands: [
ZplText(x: 10, y: 10, text: 'Hello {{name}}'),
ZplText(x: 10, y: 50, text: 'Price: \${{price}}'),
ZplBarcode(x: 10, y: 90, height: 50, data: '{{barcode}}', type: ZplBarcodeType.code128),
],
)
);
// 2. Compile geometry & imagery ONCE
await template.init();
// 3. Loop generating thousands of labels instantly
for (var dataMap in customers) {
// Zero layout/AST overhead, pure native string replacement
final rawZplPayload = template.bindSync(dataMap);
await printer.printZpl(rawZplPayload); // ZebraPrinter from flutter_zpl_printer
}
printer here is a connected ZebraPrinter from
flutter_zpl_printer.
Auto-Increment Serialization (^SN)
When printing many identical layout labels but with increasing/decreasing ID numbers (e.g. SN-001, SN-002, SN-003), sending a new payload for every single label is highly inefficient.
Instead, you can combine ZplPrintQuantity with a ZplText configured for hardware serialization. The printer will calculate and index the variable numbers internally!
final generator = ZplGenerator(
config: const ZplConfiguration(printWidth: 406, labelLength: 203),
commands: [
// This looks like static text, but we attach a 'serialization' config to it
ZplText(
x: 10,
y: 10,
text: 'SN-001', // Your starting value
serialization: const ZplSerialConfig(
increment: 1, // +1 per label copy
leadingZeros: true // Keep it exactly 3 digits long ('001' -> '002' -> '003')
),
),
// We only send 1 print job over Wi-Fi, but the printer hardware will
// eject 10 labels counting up to 'SN-010' automatically!
ZplPrintQuantity(quantity: 10),
],
);
βοΈ Hardware & Enterprise Control
Production Print Configurations (^PQ)
Purpose: In production environments, you often need to print multiple copies of the exact same label without transmitting the entire payload over Wi-Fi/Bluetooth multiple times. ZplPrintQuantity safely delegates the responsibility of duplicating the label directly to the printer's internal memory buffer.
Placement Guidelines: You can append ZplPrintQuantity anywhere inside the commands: list. It will safely execute hardware features natively before the label officially terminates (^XZ).
final generator = ZplGenerator(
config: const ZplConfiguration(printWidth: 406, labelLength: 203),
commands: [
ZplText(x: 10, y: 10, text: 'Product Label'),
// You can place this command anywhere in the 'commands' array!
// This offloads the work to the printer hardware itself:
// "Print 50 copies of this label, and physically pause after every 10."
ZplPrintQuantity(quantity: 50, pauseInterval: 10),
],
);
Enterprise RFID Tag Encoding (^RF / ^RS)
Generate "Smart Labels" by leveraging Zebra's dual-hardware printers (like the ZT411 RFID). You can encode the tiny silicon microchip hidden inside the label at the exact same time you print the visual ink barcodes!
final generator = ZplGenerator(
config: ZplConfiguration(printWidth: 406, labelLength: 203),
commands: [
// 1. Tell the printer what hardware protocol to run (EPC Class 1 Gen 2)
ZplRfidSetup(tagType: 8),
// 2. Blast your payload onto the actual RFID Antenna using HEX!
// (This encodes 11112222 directly into the EPC bank block 3)
ZplRfidWrite(
data: '11112222',
operation: RfidOperation.write,
format: RfidDataFormat.hex,
startingBlock: 3,
byteCount: 4,
memoryBank: RfidMemoryBank.epc,
),
// 3. Normal printing logic is executed in parallel!
ZplText(x: 10, y: 10, text: 'This text gets printed with ink!'),
ZplBarcode(x: 10, y: 50, data: '11112222', type: ZplBarcodeType.code128),
],
);
Note: When using
RfidDataFormat.hex, the library strictly asserts that your payload only contains completely valid[0-9A-Fa-f]characters to prevent silent printer-locking failures.
Network & Infrastructure Commands
Zebra printers operating in warehouses and production environments often require programmatic network configuration (Wi-Fi, Bluetooth, SMTP, SNMP). flutter_zpl_generator exposes a complete suite of network administration commands that gracefully format ZPL configurations without interfering with visual layout math.
These commands physically configure the hardware state. They seamlessly return 0 for layout calculations, meaning you can place them anywhere in the commands list!
final generator = ZplGenerator(
config: ZplConfiguration(printWidth: 406, labelLength: 203),
commands: [
// 1. Establish printer networking rules on boot
ZplNetworkSettings(
device: 1,
ip: '192.168.1.100',
mask: '255.255.255.0',
gateway: '192.168.1.1',
timeout: 300,
arp: 'Y',
),
// 2. Configure standard SNMP telemetry
ZplNetworkSnmp(
name: 'DockDoor_Printer_01',
location: 'Warehouse_A',
getCommunity: 'public',
),
// 3. Connect safely to a target network ID
ZplNetworkConnect(networkId: 'WLAN_INT_01'),
// Print regular graphics below...
ZplText(x: 10, y: 10, text: 'Hello ZPL Networking!'),
],
);
Available Configuration Classes:
ZplNetworkBoot(^NB) β Check interval for boot blocks.ZplNetworkDevice(^NC) β Set primary network device.ZplNetworkConnect(~NC) β Connect to a secondary network.ZplNetworkSettings(^ND) β Change network parameters.ZplNetworkId(^NI) β Assign a Network ID number string.ZplNetworkSnmp(^NN) β Configure SNMP params.ZplNetworkPrimaryDevice(^NP) β Set primary connection physical device.ZplNetworkPrintersTransparentAll(~NR) β Set all network printers transparent.ZplNetworkWiredSettings(^NS) β Wired network settings fallback.ZplNetworkPrinterTransparentCurrent(~NT) β Set current printer transparent.ZplNetworkSmtp(^NT) β Configure SMTP email rules.ZplNetworkPasswordTimeout(^NW) β Password active countdown timer.
π» Developer Experience & Examples
Live Preview Details (Labelary Integration)
You can preview the receipt in pseudo-real-time simply by wrapping your generator reference mathematically natively.
class LabelPreviewScreen extends StatelessWidget {
final generator = ZplGenerator(
config: const ZplConfiguration(printWidth: 406, labelLength: 203),
commands: [ ... ]
);
@override
Widget build(BuildContext context) {
return ZplPreview(
generator: generator, // Hot-reloads on Widget Rebuild automatically!
);
}
}
If you prefer to hit the REST API directly for PDFs or native assets:
final response = await LabelaryService.renderFromGenerator(
generator,
outputFormat: LabelaryOutputFormat.pdf,
);
// Write bytes to disk
await File('label.pdf').writeAsBytes(response.data);
Complete Use-Case: Complex Retail Receipt
Combine the 12-Unit Grid, Tables, and Barcodes to generate a fully formatted receipt effortlessly.
import 'package:flutter_zpl_generator/flutter_zpl_generator.dart';
final generator = ZplGenerator(
config: const ZplConfiguration(
printWidth: 576, // 203 DPI standard receipt
labelLength: 1200,
printDensity: ZplPrintDensity.d8,
),
commands: [
// Header
ZplText(x: 0, y: 30, text: 'RECEIPT', fontHeight: 50, fontWidth: 45),
ZplText(x: 0, y: 100, text: 'Receipt number: 117 - 44332'),
ZplText(x: 0, y: 130, text: 'Date of purchase: December 8, 2023'),
// Company & Bill To (Side-by-side using 12-unit Grid system)
ZplGridRow(
y: 200,
children: [
ZplGridCol(
width: 6, // 50% width
child: ZplColumn(
children: [
ZplText(text: 'Big Machinery, LLC', fontHeight: 25, fontWidth: 22),
ZplText(text: '3345, Diamond St, Orange City, ST 9987', fontHeight: 18, fontWidth: 16),
],
),
),
ZplGridCol(
width: 6, // 50% width
child: ZplColumn(
children: [
ZplText(text: 'Bill To', fontHeight: 25, fontWidth: 22),
ZplText(text: 'Doe John', fontHeight: 18, fontWidth: 16),
],
),
),
],
),
// Items table
ZplTable(
y: 360,
columnWidths: [6, 2, 2, 2], // 12-column grid mapping
borderThickness: 2,
cellPadding: 6,
headers: [
ZplTableHeader('Item', alignment: ZplAlignment.left, fontHeight: 22, fontWidth: 20),
ZplTableHeader('Qty', alignment: ZplAlignment.center, fontHeight: 22, fontWidth: 20),
ZplTableHeader('Unit', alignment: ZplAlignment.center, fontHeight: 22, fontWidth: 20),
ZplTableHeader('Total', alignment: ZplAlignment.center, fontHeight: 22, fontWidth: 20),
],
data: [
['Fuel Plastic Jug (10 gal)', '01', '\$34.00', '\$34.00'],
['Gas Hose (5 feet)', '01', '\$15.00', '\$15.00'],
['Aluminum Screw (4 in)', '100', '\$0.87', '\$87.00'],
],
dataFontHeight: 18,
dataFontWidth: 16,
),
// Totals
ZplText(x: 50, y: 580, text: 'Subtotal: \$136.00', fontHeight: 22, fontWidth: 20),
ZplText(x: 50, y: 650, text: 'Tax (12%): \$16.32', fontHeight: 20, fontWidth: 18),
ZplSeparator(y: 685, thickness: 2, paddingLeft: 50, paddingRight: 50),
ZplText(x: 50, y: 710, text: 'Total: \$152.32', fontHeight: 26, fontWidth: 24),
// Footer with QR code
ZplSeparator(y: 760, thickness: 1),
ZplText(x: 0, y: 785, text: 'Scan for digital receipt:', alignment: ZplAlignment.center),
ZplBarcode(
x: 0, y: 815,
data: 'https://receipt.example.com/117-44332',
type: ZplBarcodeType.qrCode,
height: 120,
alignment: ZplAlignment.center,
),
],
);
final zpl = await generator.build();
print(zpl);
FAQ
How do I generate a ZPL label in Flutter?
Add flutter_zpl_generator, list your elements (ZplText, ZplBarcode, ZplBox, β¦) in a ZplGenerator, and call await generator.build(). The result is a complete ZPL string you can send to any Zebra printer or paste into a ZPL viewer. See Quick Start.
How do I print the ZPL from a Flutter app?
This package only generates ZPL. flutter_zpl_printer sends it to Zebra printers over Bluetooth LE, Wi-Fi or USB and already depends on this package.
Can I preview a ZPL label without a printer?
Yes. ZplNativePreview draws the label on a Flutter canvas fully offline. ZplPreview renders it through the Labelary web API, and LabelaryService.renderFromGenerator returns PNG or PDF bytes.
Which barcodes are supported?
Code 128, GS1-128, Code 39, Code 93, Interleaved 2 of 5, EAN-13, EAN-8, UPC-A, UPC-E, QR Code, Data Matrix, PDF417 and Aztec. See Barcode Symbologies.
What units are coordinates in?
Printer dots. At 203 DPI (8 dots/mm) a 4Γ6 inch label is printWidth: 812, labelLength: 1218. At 300 DPI multiply inches by 300.
Does it work on web and desktop?
Yes. Generation is pure Dart and runs on Android, iOS, web, macOS, Windows and Linux.
Is there a machine-readable summary for AI coding assistants?
Yes: llms.txt at the repository root lists the core API, units and links to every guide.
Related Projects
- flutter_zpl_printer - Print these labels on Zebra printers over Bluetooth LE, Wi-Fi, and USB (includes this package)
- Labelary API - Online ZPL viewer and API
- Zebra Printers
Libraries
- flutter_zpl_generator
- Generate ZPL (Zebra Programming Language) label code in Flutter and Dart.
- widgets/zpl_preview




