Encoding guide

Generating and Scanning QR Codes Without Surprises

How QR codes are structured, how versions and encoding modes decide capacity, what error-correction levels really trade away, why a code fails to scan, and why scanning an unknown code is clicking an unknown link.

A QR code is a two-dimensional barcode with enough redundancy built in to survive being printed badly, photographed at an angle, and partly obscured. Most of the decisions you make when generating one — how much text to encode, which error-correction level to pick, how large to print it — interact with each other in ways that are invisible until a code fails in the field. This guide explains the structure well enough that those decisions stop being guesses.

What is actually inside the square

A QR symbol is a grid of modules, the black and white squares that make up the image. The three large concentric squares in the corners are finder patterns, and they exist so a decoder can locate the symbol and work out its rotation from any angle. The narrow alternating line running between two finder patterns is the timing pattern, which tells the decoder the module pitch. Larger symbols add smaller alignment patterns so the decoder can correct for perspective distortion when the code is photographed off-axis or printed on a curved surface.

Symbol sizes are called versions, numbered 1 to 40. Version 1 is 21 by 21 modules and each subsequent version adds four modules per side, so version N is 17 + 4N modules across and version 40 is 177 by 177. The last structural element is not part of the symbol at all: the quiet zone, a margin of at least four modules of background colour on every side. Omitting it is one of the most common reasons a technically perfect code will not scan, because the decoder cannot find the symbol's boundary.

Encoding modes decide how much fits

QR has four data modes and each packs characters at a different density. Numeric mode encodes three digits in 10 bits. Alphanumeric mode encodes two characters in 11 bits, but its character set is only 45 entries: the digits, uppercase A to Z, space, and the eight symbols $ % * + - . / and colon. Byte mode spends 8 bits per byte and handles anything, with UTF-8 signalled through an ECI designator. Kanji mode packs a Shift-JIS character into 13 bits.

The practical consequence is that the same information can require a noticeably different symbol depending on how it is written. A URL containing lowercase letters falls into byte mode. The same URL written entirely in uppercase can use alphanumeric mode and may fit a smaller version, which at a fixed printed size means larger modules and a more forgiving scan. This works because the scheme and host portions of a URL are case-insensitive — but the path is not, so uppercasing a path that a server treats as case-sensitive will produce a code that scans perfectly and then 404s.

The same link, two modes, two symbol sizes
Link: https://example.com/promo          (25 characters)

Byte mode, error correction L
  25 bytes needed
  version 1 holds 17 bytes  -> too small
  version 2 holds 32 bytes  -> fits
  => version 2, 25 x 25 modules

Uppercased: HTTPS://EXAMPLE.COM/PROMO    (25 characters)

Alphanumeric mode, error correction L
  25 characters needed
  version 1 holds 25 characters -> fits exactly
  => version 1, 21 x 21 modules

Same destination, 441 modules instead of 625. Printed at the
same physical width, every module is about 19% wider, which is
what actually decides whether a phone can read it at arm's length.

Caveat: scheme and host are case-insensitive, the path is not.
/PROMO and /promo are different paths on most servers.

Error correction is a trade, not a setting to max out

QR codes use Reed-Solomon error correction, and the level you choose decides what proportion of the symbol's codewords can be damaged while the data still recovers. There are four levels, conventionally quoted as roughly 7, 15, 25 and 30 percent recovery. That redundancy is not free: the correction codewords occupy the same symbol, so raising the level either shrinks the amount of data you can carry or forces a larger version.

The instinct to always choose H is usually wrong. At a fixed physical size, a version 40 symbol at level H has far more modules than a version 10 at level M, and each of those modules is much smaller. Beyond a point you have traded damage tolerance for modules too small for a phone camera to resolve, and the code becomes harder to scan, not easier. Level M is the sensible default for print and packaging; move up only when you have a specific reason.

The clearest legitimate reason is a logo overlay. Placing a graphic in the centre of a code deliberately destroys modules, and level H exists to absorb that. Keep the covered area well inside the level's budget — a logo covering more than about a fifth of the symbol area is asking for intermittent failures — and never cover a finder pattern, an alignment pattern, or the timing line, because no amount of error correction recovers a symbol the decoder cannot locate.

Capacity figures are byte mode. They show the same symbol size carrying progressively less data as redundancy rises.

LevelApproximate recoveryBytes at version 10 (57 x 57)Bytes at version 40 (177 x 177)Suited to
Labout 7%2712953Screens and clean digital display, where nothing degrades
Mabout 15%2132331The general-purpose default: print, packaging, documents
Qabout 25%1511663Labels that get handled, scuffed, or used outdoors
Habout 30%1191273Logo overlays, curved surfaces, industrial environments

Size it for the distance it will be read from

The reliable rule of thumb is a ten-to-one ratio: the printed width of the symbol should be at least one tenth of the distance it will be scanned from. A code read from half a metre needs to be about 5 cm wide; a poster read from three metres needs about 30 cm. Work backwards from the reading distance rather than from how much space the layout happens to have left.

Then check the module size that ratio implies. Divide the printed width by the module count for your version: a version 2 symbol at 5 cm gives 25 modules across, so each module is 2 mm — comfortable. The same 5 cm at version 20 gives 97 modules and a module size of about 0.5 mm, which is at the edge of what ordinary printing and an ordinary phone camera handle. If the number comes out small, reduce the data or raise the print size; do not compensate with error correction.

  • Quiet zone: at least four modules of clear background on all four sides, every time.
  • Always test the final artwork at final size, printed on the final material, with at least two different phone models — not the on-screen preview.

Why a code fails to scan

Most field failures come down to a small set of causes, and they are easy to diagnose once you know what to look for. If a code scans on screen but not in print, the problem is physical: module size, contrast, ink spread, or the surface. If it fails everywhere including on screen, the problem is in the symbol or the payload.

  • No quiet zone, or a frame or coloured panel touching the symbol edge — the single most common cause.
  • Modules too small for the reading distance, or a version far larger than the content needed.
  • Low contrast, an inverted colour scheme, or a gradient across the symbol.
  • A logo covering a finder pattern, an alignment pattern, or the timing line.
  • A stretched or non-square image: the symbol must keep a 1:1 aspect ratio.

A QR code is an opaque link

This is the part that deserves more attention than it gets. A QR code carries no authentication and no provenance whatsoever. Anyone can generate a code pointing at any destination, and nothing in the image indicates who made it or where it leads. Scanning one is exactly equivalent to clicking a link whose text you cannot read — with the additional problem that the decision to open it happens on a phone, often in a hurry, and usually outside whatever filtering protects a work computer.

The attack pattern that follows is straightforward and common: a sticker placed over a legitimate printed code on a parking meter, a restaurant table, a package, or a public notice. The surrounding context looks genuine because it is genuine; only the code was replaced. The same technique appears in email, where a code moves the target from a monitored desktop to an unmanaged personal device. Physical tampering is worth checking for directly — a sticker over a printed code, a code on a separate label that does not match the material around it, or a code applied at an angle to everything else.

Practical defences are unglamorous and effective. Let the camera app show the destination and read the domain before opening it, paying attention to the registrable domain rather than to words that appear elsewhere in the URL. Treat shortened links in a QR code with extra suspicion, since they hide the destination twice over. Never enter credentials, payment details, or a one-time code on a page you reached by scanning something unsolicited — open the site by typing the address you already know instead. And remember that codes can encode more than URLs: wifi credentials, contact records, phone numbers, and payment payloads can all prefill an action you did not intend to take.

What to remember

  • Version N is 17 + 4N modules across, so version 1 is 21 by 21 and version 40 is 177 by 177 — keep the version as low as the content allows.
  • Uppercasing the scheme and host of a URL can move it into alphanumeric mode and shrink the symbol, but never uppercase a case-sensitive path.
  • Level M is the right default; raise to Q or H for handled labels or a logo overlay, not reflexively, because more redundancy means smaller modules at a fixed size.
  • Size the printed code at roughly one tenth of the reading distance, always leave a four-module quiet zone, and test on real phones and real material.
  • Treat scanning an unknown code as clicking an unknown link: read the domain before opening, never enter credentials on a page you reached that way, and check printed codes for stickers.

Continue with related checks and tools