How to Generate Custom Barcodes (And How Check Digits Actually Work)
Barcodes look simple — lines and spaces, or a grid of squares — but the format you pick and how you print it determines whether a scanner reads it in a fraction of a second or fails repeatedly at checkout. A quick history note: the concept dates to the late 1940s (Norman Joseph Woodland and Bernard Silver, originally a circular "bullseye" design), with the rectangular UPC format standardized for retail in the 1970s.
1D vs. 2D: not just a visual difference
1D (linear) barcodes encode data in the width and spacing of vertical lines, read by a single scan line (laser or camera line). Capacity and character set vary meaningfully by format — this is the part generic guides tend to blur together:
- UPC-A / EAN-13 — numeric only, fixed length (12 or 13 digits including a check digit), used for retail products
- Code 39 — alphanumeric (uppercase letters, digits, a few symbols), lower data density, common in automotive/defense/inventory
- Code 128 — full ASCII character set, high density, the default choice for logistics and shipping labels where you need more than just digits
2D barcodes (QR codes, Data Matrix) encode data in a two-dimensional pattern, which is why they hold far more data in the same physical space and can be read from any angle. QR codes can store several kilobytes — enough for a URL, contact card, or Wi-Fi credentials, not just a short numeric ID.
Picking a format isn't just aesthetic: if you need to encode a serial number with letters in it, UPC/EAN literally can't do it (numeric only) — you'd need Code 128 or a 2D format instead.
How check digits catch scanning errors
Most numeric barcode formats include a check digit — a final digit mathematically derived from the others, used to catch a misread digit before bad data enters your system. UPC-A's check digit calculation:
def upc_check_digit(digits):
# digits: the first 11 digits of a UPC-A code, as a string
odd_sum = sum(int(d) for d in digits[0::2]) * 3 # positions 1,3,5... x3
even_sum = sum(int(d) for d in digits[1::2]) # positions 2,4,6... x1
total = odd_sum + even_sum
check_digit = (10 - (total % 10)) % 10
return check_digit
# Example: first 11 digits of a UPC-A code
print(upc_check_digit("03600029145")) # → 2, completing the code as 036000291452
If a scanner misreads even one digit, the recalculated check digit won't match the printed one, and the scan is rejected rather than silently entering wrong data into a POS or inventory system. This is why a barcode with a smudged or misprinted digit sometimes fails to scan at all rather than scanning as the wrong product — the check digit is doing its job.
Printing details that actually determine whether it scans
- Contrast: dark ink (black is safest) on a light, non-reflective background. Avoid red barcodes specifically — many laser scanners use a red light source and can't distinguish red ink from the background.
- Quiet zone: the blank margin immediately around the barcode. Scanners use it to detect where the code starts and ends; text, borders, or graphics inside that margin can make an otherwise correct barcode unreadable.
- Resolution: barcodes generated at low resolution or scaled up from a small image become blurry at print size, which is a common and avoidable cause of scan failures. Export at the print size you actually need rather than scaling after the fact.
- Test before mass printing: scan a small test batch with the actual hardware (the specific scanner or phone app) that will be used in production before printing thousands of labels.
Common use cases
- Inventory/asset tracking — unique IDs per item, batch, or location
- Point of sale — faster, typo-free checkout versus manual entry
- Event ticketing — unique per-ticket codes for entry tracking and counterfeit resistance
- Shipping/logistics — Code 128 is the common choice here due to its higher data density
Generating one
If your workflow just needs a one-off barcode rather than integration into a larger system, a browser-based generator that lets you pick the format, enter your data, and export as PNG/SVG is the fastest route — just confirm the specific format matches what your scanner or downstream system expects, since UPC/EAN, Code 39, and Code 128 aren't interchangeable for the same input data.