ZPL Programming Tutorial: Build a Professional Shipping Label from Scratch
ZPL (Zebra Programming Language) looks intimidating the first time you encounter it. A typical shipping label is 30–60 lines of terse commands with no whitespace, no comments, and no visual feedback until you print it. But once you understand the structure, writing ZPL by hand is entirely approachable — and knowing how it works is essential if you're debugging labels from a WMS, building a custom integration, or trying to understand why a barcode isn't scanning.
This tutorial builds a complete Amazon FBA-style shipping label from scratch. We start with an empty canvas and add every element step by step, explaining each command as we go. The final complete label is at the end.
Understanding the ZPL Coordinate System and Units
Before writing a single command, you need to understand how ZPL thinks about space.
Dots Per Millimeter (DPMM)
Thermal printers don't work in inches or millimeters. They work in dots — the individual heated elements in the printhead. All ZPL coordinates and dimensions are expressed in dots.
The conversion factor is the printer's resolution, expressed as dots per millimeter (DPMM):
| Resolution | DPMM | Dots per inch |
|---|---|---|
| 203 DPI | 8 DPMM | Standard; most desktop Zebra printers |
| 300 DPI | 12 DPMM | High-resolution; better for small fonts and dense barcodes |
| 600 DPI | 24 DPMM | Industrial; rarely needed for shipping labels |
A standard 4×6 inch shipping label at 203 DPI is 812 dots wide × 1218 dots tall (4 × 203 = 812, 6 × 203 = 1218). At 300 DPI, the same physical label is 1200 × 1800 dots.
All examples in this tutorial use 203 DPI (8 DPMM) unless otherwise noted.
The Coordinate System
The origin (0,0) is the top-left corner of the label. X increases to the right. Y increases downward. This is the same convention as most 2D graphics systems, and it means:
^FO0,0places a field in the top-left corner^FO406,609places a field exactly in the center of a 4×6 label at 203 DPI
The Label Wrapper: ^XA and ^XZ
Every ZPL label begins with ^XA (start of label) and ends with ^XZ (end of label). The printer ignores anything outside these tags.
zpl^XA ^XZ
That is a valid, empty ZPL label. It prints a blank label.
Key Takeaway
Key Takeaway: All ZPL coordinates are in dots, not inches or millimeters. At 203 DPI, one inch = 203 dots. At 300 DPI, one inch = 300 dots. Always know which resolution you're targeting before positioning any element.
Step 1: Set Label Dimensions and Print Width
The first thing you should always do inside ^XA is define the label dimensions. This tells the printer where the label ends so it cuts or advances correctly.
zpl^XA ^PW812 ^LL1218
^PW812— Print Width: 812 dots (4 inches at 203 DPI)^LL1218— Label Length: 1218 dots (6 inches at 203 DPI)
If you're generating ZPL for a Zentralabel API call and want the output to be a 4×6 PDF, ^PW and ^LL tell the renderer the canvas size.
Step 2: Add a Top Header Bar
Most shipping labels have a header section — a dark band at the top with the carrier or company name in reverse (white text on black).
The ^GB command draws a Graphic Box:
^GB[width],[height],[thickness],[color],[rounding]
- color is
B(black) orW(white) - rounding is 0–8 (corner radius)
zpl^XA ^PW812 ^LL1218 ^FO0,0 ^GB812,80,80,B,0^FS
This draws a solid black rectangle, 812 dots wide and 80 dots tall, starting at (0,0) — a full-width black header.
Now add white text over it using ^A (font selection) and ^FD (field data):
zpl^FO20,15 ^A0N,50,50 ^FR ^FDAmazon FBA Shipment^FS
^A0N,50,50— Font 0, Normal orientation, 50 dots tall, 50 dots wide^FR— Field Reverse: inverts the field colors so text prints white on the black background^FD...^FS— Field Data, Field Separator
Step 3: Add Sender and Recipient Address Fields
Below the header, add the ship-from and ship-to address blocks. We'll use ^CF to set a default font, which applies to all subsequent text fields until overridden.
zpl^CF0,24,20 ^FO20,100 ^FDShip From:^FS ^FO20,130 ^FDWarehouse A, 500 Commerce Dr^FS ^FO20,158 ^FDSomerville, NJ 08876^FS ^FO430,100 ^FDShip To:^FS ^FO430,130 ^FDJane Smith^FS ^FO430,158 ^FD1234 Elm Street Apt 5B^FS ^FO430,186 ^FDPortland, OR 97201^FS
^CF0,24,20 sets font 0 as the default at 24 dots tall and 20 dots wide. Each subsequent ^FD block uses that size automatically.
Adding a Horizontal Divider
Use a thin ^GB to draw a dividing line across the label:
zpl^FO0,230 ^GB812,2,2,B,0^FS
Step 4: Add the Order Details Section
A shipping label typically shows the order number, ASIN or SKU, and shipment reference. Use a slightly larger font to make the order number stand out.
zpl^CF0,28,24 ^FO20,245 ^FDOrder: 112-3456789-0123456^FS ^CF0,22,18 ^FO20,285 ^FDSKU: X-WIDGET-BLK-LG^FS ^FO20,313 ^FDQty: 1 of 3^FS ^FO430,285 ^FDCarrier: UPS Ground^FS ^FO430,313 ^FDService: Standard^FS ^FO0,345 ^GB812,2,2,B,0^FS
Step 5: Add a Code 128 Barcode (Order Barcode)
The order barcode is the primary machine-readable element. For alphanumeric order IDs, Code 128 is the standard.
The barcode width and height are controlled by ^BY (Bar code default settings):
^BY[module width],[wide bar ratio],[height]
- module width is the width of the narrowest bar in dots (2–3 is typical at 203 DPI)
- wide bar ratio is the wide-to-narrow ratio (2.0–3.0, default 3.0)
- height is barcode height in dots
zpl^FO20,365 ^BY2,3,100 ^BCN,100,Y,N,N ^FD112-3456789-0123456^FS
^BC— Code 128 barcodeN— Normal orientation (not rotated)100— Height 100 dotsY— Print human-readable text below the barcodeN,N— Do not check digit, do not print check digit in HRI
The human-readable text (Y) means the order number is printed in small font below the bars — a requirement for most FBA and carrier labels.
Step 6: Add a GS1-128 Barcode for the Tracking Number
Carrier tracking numbers (UPS, FedEx, USPS) use GS1-128, a Code 128 variant with Application Identifiers (AIs) that encode data type. ZPL handles GS1-128 through ^BC with the >8 FNC1 start character.
zpl^FO0,490 ^GB812,2,2,B,0^FS ^FO20,510 ^CF0,26,22 ^FDTracking Number:^FS ^FO20,542 ^BY2,3,90 ^BCN,90,Y,N,N ^FD>;>8420112345678901234567890^FS
The >;>8 prefix is ZPL's way of inserting the FNC1 start character that designates GS1-128. The 00 AI in 420 tells scanners this is a SSCC-18 or package-level barcode.
For simplicity in this tutorial, we're using a plain tracking number. In production, the exact AI structure depends on the carrier's specification.
Step 7: Add a DataMatrix Barcode for 2D Scanning
Many modern carrier systems and Amazon itself use DataMatrix 2D barcodes as a secondary scan target. DataMatrix stores more data in a smaller footprint and is more tolerant of print defects than linear barcodes.
In ZPL, DataMatrix is rendered via ^BX:
^BX[orientation],[height],[quality],[columns],[rows],[format],[escape char]
zpl^FO630,510 ^BXN,4,200,,,1^FS ^FD112-3456789-0123456-PORTLANDOR97201^FS
^BX— DataMatrix barcode commandN— Normal orientation4— Module height in dots (4 dots per cell)200— Quality level (error correction),,— Auto-calculate columns and rows1— Format ID 1 (standard DataMatrix)
The ^FD following ^BX provides the barcode data. This DataMatrix encodes a concatenation of order ID and destination — a common pattern for scan-based routing.
Key Takeaway
Key Takeaway: Always pair linear barcodes (Code 128) with a 2D barcode (DataMatrix or QR Code) on high-volume shipping labels. If the linear barcode is damaged in transit, the 2D barcode provides a redundant scan path.
Step 8: Add the Destination Zone and Final Dividers
The bottom section of a shipping label typically shows the destination zip code large enough for visual sort routing — human sorters glance at this in a fulfillment center.
zpl^FO0,650 ^GB812,2,2,B,0^FS ^FO20,665 ^CF0,22,18 ^FDDestination Zone:^FS ^FO20,695 ^A0N,120,100 ^FD97201^FS ^FO200,665 ^CF0,22,18 ^FDLabel Date:^FS ^FO200,695 ^CF0,30,26 ^FD2026-06-26^FS
The oversized zip code (^A0N,120,100) makes it readable at a glance from a foot away — intentional design, not an accident.
Step 9: Add a QR Code (Optional but Increasingly Common)
For returns processing and consumer-facing labels, a QR code linking to a tracking URL is increasingly standard.
zpl^FO610,665 ^BQN,2,5 ^FDQA,https://track.example.com/112-3456789-0123456^FS
^BQ— QR Code barcodeN— Normal orientation2— Error correction level M5— Magnification factor (each module is 5 dots wide)QA,— Model 2, auto error correction; the URL follows after the comma
The Complete Label: Final ZPL Code
Here is the entire label assembled. You can paste this directly into Labelary to preview it, or upload it to Zentralabel to convert it to PDF.
zpl^XA ^PW812 ^LL1218 ^FO0,0 ^GB812,80,80,B,0^FS ^FO20,15 ^A0N,50,50 ^FR ^FDAmazon FBA Shipment^FS ^CF0,24,20 ^FO20,100 ^FDShip From:^FS ^FO20,130 ^FDWarehouse A, 500 Commerce Dr^FS ^FO20,158 ^FDSomerville, NJ 08876^FS ^FO430,100 ^FDShip To:^FS ^FO430,130 ^FDJane Smith^FS ^FO430,158 ^FD1234 Elm Street Apt 5B^FS ^FO430,186 ^FDPortland, OR 97201^FS ^FO0,230 ^GB812,2,2,B,0^FS ^CF0,28,24 ^FO20,245 ^FDOrder: 112-3456789-0123456^FS ^CF0,22,18 ^FO20,285 ^FDSKU: X-WIDGET-BLK-LG^FS ^FO20,313 ^FDQty: 1 of 3^FS ^FO430,285 ^FDCarrier: UPS Ground^FS ^FO430,313 ^FDService: Standard^FS ^FO0,345 ^GB812,2,2,B,0^FS ^FO20,365 ^BY2,3,100 ^BCN,100,Y,N,N ^FD112-3456789-0123456^FS ^FO0,490 ^GB812,2,2,B,0^FS ^CF0,26,22 ^FO20,510 ^FDTracking Number:^FS ^FO20,542 ^BY2,3,90 ^BCN,90,Y,N,N ^FD>;>8420112345678901234567890^FS ^FO630,510 ^BXN,4,200,,,1^FS ^FD112-3456789-0123456-PORTLANDOR97201^FS ^FO0,650 ^GB812,2,2,B,0^FS ^CF0,22,18 ^FO20,665 ^FDDestination Zone:^FS ^FO20,695 ^A0N,120,100 ^FD97201^FS ^FO200,665 ^CF0,22,18 ^FDLabel Date:^FS ^FO200,695 ^CF0,30,26 ^FD2026-06-26^FS ^FO610,665 ^BQN,2,5 ^FDQA,https://track.example.com/112-3456789-0123456^FS ^XZ
ZPL Command Quick Reference
| Command | Purpose | Example |
|---|---|---|
^XA / ^XZ | Start / end label | ^XA ... ^XZ |
^PW | Print width in dots | ^PW812 |
^LL | Label length in dots | ^LL1218 |
^FO | Field origin (X,Y) | ^FO50,100 |
^CF | Default font | ^CF0,24,20 |
^A | Font for one field | ^A0N,40,30 |
^FD...^FS | Field data | ^FDHello^FS |
^FR | Field reverse (invert) | ^FR |
^GB | Graphic box (line/rect) | ^GB400,2,2,B,0^FS |
^BY | Bar code defaults | ^BY2,3,100 |
^BC | Code 128 barcode | ^BCN,100,Y,N,N |
^BX | DataMatrix barcode | ^BXN,4,200,,,1^FS |
^BQ | QR Code | ^BQN,2,5 |
Rendering and Converting Your ZPL
Once you've built your label, you need to see it. For development, paste the ZPL into Labelary's viewer and choose 8 DPMM (203 DPI) with a 4×6 label size.
For production use — generating PDFs, running batch conversions, or integrating with a fulfillment pipeline — Zentralabel accepts ZPL via file upload or API and returns PDF, PNG, or SVG output with configurable DPI and label dimensions.
Now that you can write ZPL by hand, you have everything you need to debug any label from any WMS, customize carrier label templates, or build your own label generation logic from scratch. Try converting your label with Zentralabel →
