How a QR code encodes data

A QR code is text in a box: every generator on this site writes what you type as a string of text laid out to a published convention, then hands that text to an encoder that draws the symbol. Those are two separate layers, and most questions about a code that "doesn't work" are answered by knowing which layer the problem lives in. The examples below use invented networks, people and addresses, and the site's own engine builds them when the page is built: every payload, version, module count, contrast ratio and print size here is computed, not typed, so the guide cannot disagree with the generator it describes.

Two layers: the payload and the symbol

The payload is the text encoded inside the code — a URL, a WIFI: string, a vCard. The symbol is the black-and-white matrix itself, produced according to the QR standard (ISO/IEC 18004) with built-in error correction. The engine builds the payload exactly right, and the symbol is produced by a vendored copy of the reference QR encoder (Project Nayuki's qrcodegen), kept byte-comparable with upstream.

The split matters because the layers know nothing about each other: the encoder packs bytes into modules and adds recovery data without reading the payload for meaning. Most "my code doesn't work" problems are payload problems, which is why this guide starts there.

Layer one: the payload is text written to a convention

A WiFi network or a contact has no QR format of its own, only a convention — a text layout published somewhere specific — and each builder follows the source for its type. The spec sources were verified against the live documents on 2026-07-20:

  • Links encode exactly the text of the URL, as the ZXing "Barcode Contents" wiki describes, and the protocol must be included so readers recognise it as a URL. The engine validates the address and never rewrites it — no shortener, no tracking redirect.
  • WiFi uses the WIFI:T:<auth>;S:<ssid>;P:<password>;; convention from the same wiki. The characters \ ; , : " are backslash-escaped, and an SSID or password made entirely of hex digits is wrapped in double quotes so readers take it as literal text rather than hex-encoded bytes. WPA2 and WPA3 are both written as T:WPA, because the joining device negotiates the strongest protocol itself and a nonstandard token would break parsers (the WiFi payload guide takes the format apart).
  • Contact cards are vCard 3.0 per RFC 2426 — chosen over MECARD for broader field support and better scanner coverage — with CRLF line endings and \ ; , and newline escaped in text values (see the vCard guide).
  • Email, phone, SMS, locations and events use the standard URI and calendar formats — mailto: (RFC 6068), tel: (RFC 3966, number in E.164 form), sms: (RFC 5724, ?body=), geo: (RFC 5870), iCalendar (RFC 5545) — with user-supplied text percent-encoded where the scheme requires it (see the URI schemes guide).
  • Plain text is passed through unchanged; the QR byte segment carries UTF-8.

Worked example: a WiFi payload

An invented network Harbour Cafe Guest, WPA2, with the invented password salt;marsh:42, becomes this exact text:

WIFI:T:WPA;S:Harbour Cafe Guest;P:salt\;marsh\:42;;

The semicolon and colon inside the password each gained a backslash so they cannot be read as field separators, and WPA2 was written as T:WPA. The symbol is version 4, 33×33 modules, error correction M.

The builders check format, not truth. They reject an empty SSID, a URL without a protocol or a phone number without a country code — but they cannot know whether a WiFi password is correct, a phone number reachable, or a URL safe. A well-formed payload containing a wrong password produces a well-formed, useless code.

Layer two: the symbol — version and modules

Once the payload exists, the encoder chooses a version — the symbol's size class, between 1 and 40 — picking the smallest whose capacity holds the payload at the requested error-correction level. The version fixes the number of modules, the small squares, along each side: version 1 is 21×21, and every version step adds 4 modules to each side. The engine's choice for the four fictional payloads on this page, all requested at the default level M — the last column shows the level actually encoded, which the error-correction section explains:

Payload Characters Version Modules per side Error correction
Plain text 5 1 21 H
Link (URL) 32 3 29 Q
WiFi network 51 4 33 M
Contact card (vCard) 177 9 53 M

The vCard is the longest payload by far, so it lands on the highest version and the densest symbol: more modules at the same output size means smaller modules, which the print-size section below puts numbers on.

Worked example: a link

The fictional address https://example.com/autumn-menu/ is encoded exactly as entered:

https://example.com/autumn-menu/

Version 3, 29×29 modules, error correction Q — rendered by the same engine as the URL generator.

If a payload will not fit even at version 40, the engine does not truncate it; it returns "This content is too long for a QR code. Shorten it or lower the error correction level." — the second half of which points at the next layer.

Error correction: L, M, Q and H as the engine exposes them

Recovery data travels with the payload so a damaged or partly obscured code can still be read. The engine exposes four levels, L, M, Q and H, in increasing order of recovery; the default is M, and any of the four can be chosen when no logo is present. Add a logo and the choice disappears: a centre logo destroys modules that error correction must rebuild, so the engine caps the logo at 16% of the symbol area and always encodes at H. The error correction and logos guide covers how that cap was decode-tested.

More recovery data has to live somewhere, so a higher level can push the same payload to a higher version — though not always, as the next paragraph shows. The fictional vCard does at every step:

Requested level Level encoded Version Modules per side
L L 8 49
M M 9 53
Q Q 11 61
H H 13 69

The level the engine reports is never lower than the one requested, but it may be higher: the reference encoder boosts the level whenever that does not increase the version. The text "Hello" requested at level L comes back at level H, still version 1. The link above, requested at the default M, is reported as Q at version 3 for the same reason. Worked examples across the site print the level actually encoded.

Reading the facts under a worked example

The quiet zone. The standard requires a 4-module light border on all four sides. The renderer always draws it and offers no way to turn it off, because cropping it is the most common self-inflicted scan failure. So the link symbol above, 29×29 modules on its own, is 37×37 modules as downloaded, and the vCard is 61×61.

Print size. The PNG download offers 3 pixel sizes, and for each the generator shows the physical size at 300 DPI:

  • 512 px — 1.71 in × 1.71 in (43.3 mm × 43.3 mm) at 300 DPI
  • 1024 px — 3.41 in × 3.41 in (86.7 mm × 86.7 mm) at 300 DPI
  • 2048 px — 6.83 in × 6.83 in (173.4 mm × 173.4 mm) at 300 DPI

Pixels are shared out across the modules, quiet zone included, and the PNG export sizes each module by that one division: the 1024-pixel preset divided by the link symbol's 37 modules gives about 27.7 pixels per module, and the same preset divided by the vCard's 61 gives about 16.8 — the version table again, seen from the printer's side. Error correction helps a damaged code scan, but print size and contrast still matter; the quiet zone and print size guide works through every preset.

Contrast. Colour is applied after the module pattern is fixed, so it cannot change the data. The style panel computes the WCAG relative-luminance contrast ratio of your pair and warns below 4.5:1, and warns separately for light-on-dark (inverted) codes, which many scanners reject. Black on white measures 21.00:1 with no warning. A pale grey #9aa3ad on white measures 2.56:1 and produces: "Low contrast (2.56:1). Scanners may fail below 4.5:1 — darken the foreground or lighten the background." White on black measures 21.00:1 — the same number — and still produces: "Light-on-dark (inverted) codes are not reliably read by all scanners. Prefer dark modules on a light background." (More pairs are in the contrast guide.)

Why payload mistakes dominate

The symbol layer is reference code, and a round-trip suite renders each payload type to an image, rasterises it, and decodes it with an independent QR decoder; the decoded text must match the payload byte for byte, including a worst-case run with a logo obscuring the symbol's centre. When that suite passes, the matrix is right. What it cannot check is whether the text you asked for was the text you meant.

A wrong password encodes fine. The network from the first worked example with its password mistyped as salt;marsh:24 is a different payload of the same length, and the engine encodes it as version 4, 33×33 modules, error correction M — figures identical to the correct code's. The facts printed under a code describe the container, never the contents.

A link without its protocol is refused, not guessed. Type example.com/autumn-menu/ and the builder returns "Include the protocol (e.g. https://) so scanners recognize the link." rather than silently prepending something. That is a format rule it can enforce; whether the page at that address is the one you intended, it cannot.

The payload is frozen at print time. Every code this site produces is static — the data lives in the image itself, with no redirect through any server — so it never expires and cannot be held hostage, but it also cannot be edited after printing, and scan counts cannot be tracked. If the password changes, the printed code is a correct symbol of the wrong payload, and only a new code fixes it; the static versus dynamic guide sets out that trade-off.

Treat a freshly generated code as untested until a real device has scanned it. The symbol facts say the code was built correctly; only a scan says it was built from the right text.

Encoding FAQ

Does a longer payload always make a bigger symbol?

The encoder picks the smallest version that holds the payload at the requested error-correction level, so a longer payload needs a higher version only once it crosses the current version's capacity; within that capacity the symbol is unchanged. The 5-character text "Hello" and the 12-character "Hello, world" both encode as version 1, 21×21 modules. The fictional link on this page (32 characters) encodes as version 3, 29×29 modules; the fictional contact card (177 characters) needs version 9, 53×53 modules when requested at the same level. Versions run from 1 to 40; past the largest, the engine refuses rather than truncating.

What do "version", "modules" and "error correction" under a worked example mean?

Version is the size class the encoder chose; modules are the squares along one side, not counting the 4-module quiet zone the renderer adds on every edge; error correction is the recovery level actually encoded, one of L, M, Q or H. All three describe the symbol only — they say nothing about whether the payload text is correct.

Why does the engine report a higher error-correction level than I selected?

The reference encoder raises the level whenever doing so does not increase the version, so the reported level is never lower than the one requested but may be higher. The text "Hello" requested at level L is encoded at level H, still at version 1.

If I type the wrong WiFi password, will the code still generate?

Yes. The engine validates format, not truth: it cannot know whether a password is correct, a number reachable, or a URL safe. The fictional network with password "salt;marsh:42" and the same network with "salt;marsh:24" both encode as version 4, 33×33 modules at level M — one is right, one is wrong, and nothing in the symbol can tell them apart.

Can I crop the white border to make the code smaller?

No. The 4-module white border is the quiet zone the standard requires; the renderer draws it into every download and it cannot be disabled, because cropping it is the most common self-inflicted scan failure. The link symbol on this page is 29×29 modules on its own and 37×37 with its quiet zone — the larger square is what to print.

Does choosing different colours change the encoded data?

No. Colour is applied by the renderer after the module pattern is fixed, so the data and version are unchanged. The style panel computes the WCAG contrast ratio of your pair and warns below 4.5:1, and separately for light-on-dark (inverted) codes: black on white measures 21.00:1 with no warning, while the same colours swapped trigger the inverted warning.

Everything this guide describes runs in your browser when you use the generator, and the other guides take each layer further. How the encoding is built and verified is on the methodology page.