BlogEngineering

ZPL Programming Tutorial: Build a Professional Shipping Label from Scratch

A hands-on ZPL programming tutorial that walks you through building a complete Amazon FBA-style shipping label from a blank canvas, covering coordinates, fonts, barcodes, graphic elements, and final output.

2026-06-26
14 min read
Zentralabel Team
SHARE

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):

ResolutionDPMMDots per inch
203 DPI8 DPMMStandard; most desktop Zebra printers
300 DPI12 DPMMHigh-resolution; better for small fonts and dense barcodes
600 DPI24 DPMMIndustrial; 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,0 places a field in the top-left corner
  • ^FO406,609 places 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) or W (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 barcode
  • N — Normal orientation (not rotated)
  • 100 — Height 100 dots
  • Y — Print human-readable text below the barcode
  • N,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 command
  • N — Normal orientation
  • 4 — Module height in dots (4 dots per cell)
  • 200 — Quality level (error correction)
  • ,, — Auto-calculate columns and rows
  • 1 — 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 barcode
  • N — Normal orientation
  • 2 — Error correction level M
  • 5 — 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

CommandPurposeExample
^XA / ^XZStart / end label^XA ... ^XZ
^PWPrint width in dots^PW812
^LLLabel length in dots^LL1218
^FOField origin (X,Y)^FO50,100
^CFDefault font^CF0,24,20
^AFont for one field^A0N,40,30
^FD...^FSField data^FDHello^FS
^FRField reverse (invert)^FR
^GBGraphic box (line/rect)^GB400,2,2,B,0^FS
^BYBar code defaults^BY2,3,100
^BCCode 128 barcode^BCN,100,Y,N,N
^BXDataMatrix barcode^BXN,4,200,,,1^FS
^BQQR 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 →

ZPLTutorialBarcodeLabel DesignCode
Try the tool

Automate this in seconds with Zentralabel

Convert ZPL to PDF/PNG instantly. No setup required.

Start Free
Z
Zentralabel Team
The Zentralabel team builds label automation tools for Amazon sellers and 3PLs. We share tactical guides on ZPL, fulfillment, label routing, and Seller Central workflows.