Generate barcode labels

2025-08-07

Put the power of modern barcodes into your own hands

Image for post

Barcode Generation with Python, HTML, and CSS

Here’s a complete MDX article you can drop in as-is. It focuses on generating QR codes with segno and laying out printable, professional-looking barcode/QR labels using plain HTML + CSS. It also includes a small Python script that reads a CSV (or JSON) and outputs both image files and a printable labels page.


How it works

Modern teams can generate and print their own labels, no SaaS required.

Note: segno generates QR codes (and Micro QR). For linear barcodes (Code128/EAN/UPC), pair this with a library like python-barcode. This article focuses on QR codes with segno.

Prerequisites

Install dependencies
python -m venv .venv && source .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install segno pandas

Quick start: one QR in 3 lines

./quick_qr.py
import segno
qr = segno.make('https://example.com/products/ABC-123', error='h')  # 'h' = high error correction
qr.save('qr-ABC-123.svg', scale=10, border=2)  # SVG scales perfectly for print

Styling: colors, quiet zone, and size

./qr_styling.py
import segno
 
qr = segno.make('INV-000456', error='m')
qr.save(
    'invoice-qr.svg',
    scale=12,          # module pixel size (if saving raster, this is pixels; SVG scales infinitely)
    border=4,          # quiet zone (light margin) in modules
    dark='#111111',    # QR “ink” color
    light='#ffffff'    # QR background
)

Embedding as a data URI (no image files!)

You can inline the QR as an SVG data URI—great for single-file HTML labels.

./qr_data_uri.py
import segno
qr = segno.make('https://example.com/p/sku123')
data_uri = qr.svg_data_uri(scale=5)   # 'scale' sets module size
print(data_uri)                       # paste into <img src="...">

Batch: from CSV to images + printable labels page

This script:

  1. Reads labels.csv with columns like: sku,title,url.
  2. Generates SVG and PNG for each row.
  3. Builds a printable HTML grid with embedded data URIs (so it’s one self-contained file).
  4. Gives you CSS tuned for common Avery-style label sheets (you can tweak sizes).
./generate_labels.py
import csv
import html
import segno
from pathlib import Path
 
INPUT_CSV = Path("labels.csv")  # sku,title,url
OUT_DIR    = Path("out")
OUT_DIR.mkdir(parents=True, exist_ok=True)
 
rows = []
with INPUT_CSV.open(newline="", encoding="utf-8") as f:
    reader = csv.DictReader(f)
    for r in reader:
        sku   = (r.get("sku") or "").strip()
        title = (r.get("title") or "").strip()
        url   = (r.get("url") or "").strip()
        if not sku or not url:
            continue
 
        qr = segno.make(url, error="h", micro=False)
        # Save discrete assets if you want files
        qr.save(OUT_DIR / f"{sku}.svg", scale=10, border=2)
        qr.save(OUT_DIR / f"{sku}.png", scale=8, border=2)
 
        # Build data URI for single-file printable HTML
        data_uri = qr.svg_data_uri(scale=5)  # scale => module size inside label
        rows.append({
            "sku": sku,
            "title": title,
            "url": url,
            "data_uri": data_uri
        })
 
# Create printable labels page
html_items = []
for r in rows:
    safe_title = html.escape(r["title"] or r["sku"])
    safe_sku   = html.escape(r["sku"])
    safe_url   = html.escape(r["url"])
    img_tag    = f'<img alt="QR for {safe_sku}" src="{r["data_uri"]}" />'
    html_items.append(f"""
    <div class="label">
      <div class="qr">{img_tag}</div>
      <div class="meta">
        <div class="title">{safe_title}</div>
        <div class="sku">{safe_sku}</div>
        <div class="url">{safe_url}</div>
      </div>
    </div>
    """)
 
html_doc = f"""<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Printable Labels</title>
<style>
  @page {{
    size: A4;               /* change to 'Letter' if in the US */
    margin: 10mm;           /* tune for your stock */
  }}
  :root {{
    --label-w: 64mm;        /* label width */
    --label-h: 34mm;        /* label height */
    --gap-x:   4mm;         /* horizontal gap between labels */
    --gap-y:   4mm;         /* vertical gap between labels */
  }}
  * {{ box-sizing: border-box; }}
  body {{
    margin: 0;
    font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, "Helvetica Neue", Arial;
  }}
  .sheet {{
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(var(--label-w),1fr));
    gap: var(--gap-y) var(--gap-x);
    padding: 8mm;
  }}
  .label {{
    width: var(--label-w);
    height: var(--label-h);
    border: 0.2mm solid #ddd;
    border-radius: 2mm;
    padding: 3mm;
    display: grid;
    grid-template-columns: auto 1fr;
    grid-template-rows: 1fr;
    align-items: center;
    overflow: hidden;
  }}
  .label .qr {{
    display: flex;
    align-items: center;
    justify-content: center;
    padding-right: 3mm;
  }}
  .label img {{
    display: block;
    width: 28mm;     /* size of QR inside label; adjust to fit */
    height: 28mm;
  }}
  .label .meta {{
    display: grid;
    gap: 1mm;
    min-width: 0;
  }}
  .label .title {{
    font-size: 3.4mm;
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }}
  .label .sku {{
    font-size: 3.2mm;
    letter-spacing: 0.1mm;
  }}
  .label .url {{
    font-size: 2.6mm;
    color: #555;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }}
 
  @media print {{
    .label {{ border-color: transparent; }}  /* hide borders for final print */
    .sheet {{ padding: 0; }}
  }}
</style>
</head>
<body>
  <div class="sheet">
    {''.join(html_items)}
  </div>
</body>
</html>
"""
 
outfile = OUT_DIR / "labels.html"
outfile.write_text(html_doc, encoding="utf-8")
print(f"Wrote {outfile.resolve()}")

Sample labels.csv

./labels.csv
sku,title,url
SKU-001,Widget Alpha,https://example.com/products/widget-alpha
SKU-002,Widget Beta,https://example.com/products/widget-beta
SKU-003,Widget Gamma,https://example.com/products/widget-gamma

Run the generator:

python generate_labels.py
# => out/SKU-001.svg, out/SKU-001.png, ..., and out/labels.html

Open out/labels.html in your browser and Print (⌘/Ctrl + P). If you’re using pre-cut label sheets:


Tips for resilient QR labels


Micro QR Codes (very small labels)

When space is tight, try Micro QR. It encodes less data but fits tiny labels:

./micro_qr.py
import segno
qr = segno.make_micro('INV-457')      # segno chooses the smallest micro version that fits
qr.save('micro-invoice.svg', scale=14, border=2)

Embedding QR into your app workflow

Example tokenized payload with high error correction:

./rma_qr.py
import secrets, segno
token = secrets.token_urlsafe(16)
qr = segno.make(f"https://your.app/rma?t={token}", error='h')
qr.save("rma.svg", scale=10, border=3)

Verifying scan quality


Bonus: mixing QR with linear barcodes

If you need both QR and a linear barcode (e.g., Code128 for scanners, QR for phones), add python-barcode and render beneath the QR:

pip install python-barcode pillow
./qr_plus_code128.py
import segno, barcode
from barcode.writer import ImageWriter
from pathlib import Path
 
payload = "SKU-001"
segno.make(payload, error='m').save("out/sku-001-qr.svg", scale=8, border=2)
 
Path("out").mkdir(exist_ok=True, parents=True)
code128 = barcode.get("code128", payload, writer=ImageWriter())
code128.save("out/sku-001-code128", options={"module_height": 12.0, "quiet_zone": 2.0})

Then position both in your label HTML with simple CSS.


Troubleshooting


Wrap-up

You now have a reproducible pipeline to generate, layout, and print professional QR labels with open tools. Tweak the CSS to your exact paper stock, and hook the Python scripts into your ops flow (CI, cron, or a button in your internal tools).