Python Barcode Generation — Deep Dive
How 1D barcodes encode data
A 1D barcode is a sequence of bars and spaces of varying widths. Each symbology defines a mapping from characters to bar/space patterns:
EAN-13 encoding:
The 13-digit code is split into a leading digit, a left group of 6 digits, and a right group of 6 digits. The leading digit determines which of two encoding sets (L and G) each left-group digit uses. Right-group digits always use the R encoding. Each digit maps to a 7-module pattern (e.g., “0” in L-encoding = 0001101).
The result is framed by start, center, and end guard patterns — fixed bar sequences that tell the scanner where groups begin and end.
Code 128 encoding:
Code 128 uses three character sets (A: control + uppercase, B: full printable ASCII, C: digit pairs). The encoder starts with a start character indicating the initial set, then switches sets mid-stream as needed with shift characters. Each data character is 11 modules wide (3 bars + 3 spaces). A modulo-103 check character precedes the stop pattern.
The python-barcode library implements these algorithms in pure Python. The encode() method returns a string of 1s and 0s representing bar and space modules, which the writer renders into an image.
Custom writers
The library’s writer system is extensible. Built-in writers include SVGWriter (default) and ImageWriter (Pillow-based). Create a custom writer by subclassing BaseWriter:
from barcode.writer import BaseWriter
class JSONWriter(BaseWriter):
def __init__(self):
super().__init__(self._init, self._paint_module, self._paint_text, self._finish)
self.modules = []
def _init(self, code):
self.modules = []
def _paint_module(self, xpos, ypos, width, color):
self.modules.append({
'x': xpos, 'y': ypos,
'width': width, 'color': color
})
def _paint_text(self, xpos, ypos):
pass # skip text rendering
def _finish(self):
import json
return json.dumps(self.modules)
This pattern is useful for generating barcodes in custom formats — for example, producing drawing commands for a thermal printer SDK or generating coordinates for a laser engraver.
GS1 compliance
GS1 is the international standards body for barcodes in retail and logistics. Compliance requires:
- Correct symbology — EAN-13 for retail, GS1-128 for logistics
- Proper sizing — EAN-13 nominal size is 37.29mm × 25.93mm; allowed magnification range is 80%-200%
- X-dimension — the width of the narrowest bar. EAN-13 nominal is 0.33mm; minimum is 0.264mm
- Quiet zones — minimum white space on each side (EAN-13 requires 11 modules left, 7 modules right)
- Bar height — minimum 22.85mm at 100% magnification
Configure python-barcode for GS1 compliance:
options = {
'module_width': 0.33, # GS1 nominal X-dimension in mm
'module_height': 22.85, # GS1 minimum bar height
'quiet_zone': 3.63, # 11 × 0.33mm for EAN-13 left zone
'dpi': 300,
}
For GS1-128 (used in shipping labels), encode Application Identifiers (AIs) within the data:
# AI (01) = GTIN, AI (17) = Expiry date, AI (10) = Batch
data = "(01)09501234567890(17)260401(10)BATCH123"
# Strip parentheses for encoding; they're human-readable only
encoded_data = "0109501234567890172604011012BATCH123"
code = barcode.get('code128', encoded_data, writer=ImageWriter())
Label sheet generation
For printing on standard label sheets (Avery 5160, etc.), use reportlab to lay out barcodes in a grid:
from reportlab.lib.pagesizes import letter
from reportlab.lib.units import inch, mm
from reportlab.pdfgen import canvas
from reportlab.graphics.barcode import code128
import barcode as pybarcode
from io import BytesIO
from PIL import Image
def generate_label_sheet(items, output_pdf, labels_per_row=3, labels_per_col=10):
c = canvas.Canvas(output_pdf, pagesize=letter)
page_w, page_h = letter
margin_x, margin_y = 0.19 * inch, 0.5 * inch
label_w = (page_w - 2 * margin_x) / labels_per_row
label_h = (page_h - 2 * margin_y) / labels_per_col
for idx, item in enumerate(items):
if idx > 0 and idx % (labels_per_row * labels_per_col) == 0:
c.showPage()
pos = idx % (labels_per_row * labels_per_col)
col = pos % labels_per_row
row = pos // labels_per_row
x = margin_x + col * label_w + 5 * mm
y = page_h - margin_y - (row + 1) * label_h + 5 * mm
bc = code128.Code128(item['code'], barWidth=0.3*mm, barHeight=10*mm)
bc.drawOn(c, x, y + 5 * mm)
c.setFont("Helvetica", 7)
c.drawString(x, y, item['name'][:30])
c.save()
High-volume generation
For generating millions of barcodes (warehouse inventory, event tickets):
from concurrent.futures import ProcessPoolExecutor
import barcode
from barcode.writer import ImageWriter
def generate_one(args):
code_data, output_path = args
bc = barcode.get('code128', code_data, writer=ImageWriter())
bc.save(output_path)
return output_path
items = [(f"ITEM-{i:08d}", f"output/barcode_{i:08d}") for i in range(100000)]
with ProcessPoolExecutor(max_workers=8) as executor:
results = list(executor.map(generate_one, items))
Performance tips:
- SVG is faster than PNG — no rasterization step
- Process pool > thread pool — barcode generation is CPU-bound (GIL limitation)
- Batch to PDF — generating one PDF with 1000 barcodes is faster than 1000 individual images
- Cache the writer — reusing a single ImageWriter avoids repeated Pillow initialization
Thermal printer integration
Thermal label printers (Zebra, DYMO, Brother) often accept ZPL (Zebra Programming Language) or ESC/POS commands. Generate barcodes directly in these formats:
# ZPL barcode command for Zebra printers
def to_zpl(data, barcode_type='C', height=100):
return f"""
^XA
^FO50,50
^BY2
^B{barcode_type},{height},Y,N,N
^FD{data}^FS
^XZ
"""
# Send to printer via network
import socket
def print_zpl(zpl_string, printer_ip, port=9100):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((printer_ip, port))
s.send(zpl_string.encode())
For DYMO printers, use the dymoprint library or generate images at the printer’s native DPI (usually 300) and send via the CUPS driver.
2D barcode alternatives
While python-barcode focuses on 1D barcodes, production systems often need 2D formats:
- QR Code — use the
qrcodelibrary (see python-qrcode-generation) - Data Matrix — use
pylibdmtxfor encoding and decoding - PDF417 — use
pdf417genfor this stacked 2D format common in ID cards and boarding passes - Aztec — less common in Python; consider
zxingvia subprocess
Scanner integration and validation
For automated quality control, scan generated barcodes programmatically:
from pyzbar.pyzbar import decode
from PIL import Image
def validate_barcode(image_path, expected_data, expected_type='EAN13'):
img = Image.open(image_path)
results = decode(img)
if not results:
return False, "No barcode detected"
for result in results:
if result.type == expected_type and result.data.decode() == expected_data:
return True, "Valid"
actual = [(r.type, r.data.decode()) for r in results]
return False, f"Mismatch: expected {expected_type}/{expected_data}, got {actual}"
Quality metrics to check:
- Decode success rate — scan each barcode at least 3 times from different angles
- Quiet zone compliance — ensure sufficient white space around the barcode
- Print contrast signal (PCS) — measure the difference between bar and space reflectance
- Edge determination — verify bars have sharp, clean edges at the target DPI
Error handling patterns
import barcode
def safe_generate(symbology, data, output_path, **options):
try:
bc_class = barcode.get_barcode_class(symbology)
except barcode.errors.BarcodeNotFoundError:
raise ValueError(f"Unknown symbology: {symbology}")
try:
bc = bc_class(data, writer=ImageWriter())
except barcode.errors.NumberOfDigitsError as e:
raise ValueError(f"Invalid data length for {symbology}: {e}")
except barcode.errors.IllegalCharacterError as e:
raise ValueError(f"Invalid character for {symbology}: {e}")
return bc.save(output_path, options=options)
Common errors:
- Wrong digit count for fixed-length symbologies (EAN-13 needs 12 or 13 digits)
- Non-numeric characters in numeric-only symbologies
- Check digit mismatch when providing the full code
- Missing Pillow when using ImageWriter
The one thing to remember: Production barcode generation requires GS1 compliance awareness, programmatic scan validation, proper sizing for the target print medium, and error handling for data format mismatches — the library handles encoding, but the system around it determines whether the printed result actually scans reliably.
See Also
- Python Arcade Library Think of a magical art table that draws your game characters, listens when you press buttons, and cleans up the mess — that's Python Arcade.
- Python Audio Fingerprinting Ever wonder how Shazam identifies a song from just a few seconds of noisy audio? Audio fingerprinting is the magic behind it, and Python can do it too.
- Python Cellular Automata Imagine a checkerboard where each square follows simple rules to turn on or off — and suddenly complex patterns emerge like magic.
- Python Godot Gdscript Bridge Imagine speaking English to a friend who speaks French, with a translator in the middle — that's how Python talks to the Godot game engine.
- Python Librosa Audio Analysis Picture a music detective that can look at any song and tell you exactly what notes, beats, and moods are hiding inside — that's what Librosa does for Python.