Error correction and logos

Error correction is a property of the QR symbol, not of the text inside it. When the encoder lays your payload out as a grid, it also writes redundancy codewords into that grid, so a reader that loses some modules — to wear, or to a logo placed there on purpose — can rebuild them and recover the exact payload. This guide explains the four levels the engine exposes, what choosing one actually changes, why a centre logo forces the highest level, and where the 16% logo cap comes from. Every figure below is produced by the same engine that powers the generator, at build time, from the fictional inputs shown.

Where error correction sits

A QR code has two independent layers. The payload is text built to a convention — a URL, a WIFI: string, a vCard. The symbol is the black-and-white matrix produced to ISO/IEC 18004 by a vendored copy of the reference encoder (Project Nayuki's qrcodegen), kept byte-comparable with upstream. Error correction belongs entirely to the second layer. It makes a damaged symbol decodable; it has no opinion about whether the payload was right. A WiFi code whose password carries a typo encodes perfectly at any level and decodes perfectly — to the wrong password. The engine validates format, not truth, and most "my code doesn't work" problems are payload problems rather than symbol problems.

The four levels as the engine exposes them

The engine's error-correction type has four values: L, M, Q and H, in rising order of recovery. encodeQr takes a requested level, defaults to M when none is given, and hands the text to the encoder. Two rules from the engine matter when you read what comes back:

  • The encoded level is never lower than requested, but may be higher. The reference encoder boosts the level when doing so does not increase the version — extra recovery that fits in the grid you were getting anyway.
  • A level is selectable only when no logo is present. With a logo the level is forced, for reasons the next section explains.

The table encodes three fictional payloads — a link to https://example.com/menu, a WiFi network named LibraryGuest with the password turn-the-page-7, and a contact card for "Sample Contact" of Example Bakery (a vCard 3.0 string per RFC 2426 — see the vCard guide) — at each requested level, and reports what the encoder actually produced.

Payload Requested Encoded Version Modules
Link L M 2 25×25
Link M M 2 25×25
Link Q H 3 29×29
Link H H 3 29×29
WiFi L L 3 29×29
WiFi M Q 4 33×33
WiFi Q Q 4 33×33
WiFi H H 6 41×41
Contact card L L 8 49×49
Contact card M M 9 53×53
Contact card Q Q 11 61×61
Contact card H H 13 69×69

Read the "Requested" and "Encoded" columns together. 3 of the 12 requests came back at a higher level than asked for — the Link payload requested at L was encoded at M, for instance — because the stronger level fitted the same version and the encoder took it. That is why the generator reports the level it encoded rather than the level you picked.

The contact card shows the price of recovery. Its version climbs from 8 at L to 13 at H, and the grid from 49×49 to 69×69 modules. More recovery means more codewords; more codewords mean a larger version; a larger version means more, smaller modules in the same printed square. The level is not free; the engine defaults to M and leaves the choice to you when no logo is present.

Why a logo forces H

A centre logo is deliberate damage. The renderer does not draw your image over the modules; it first paints a background-coloured knockout square — the image plus a pad of one module on each side — and every module under that square is simply gone. Whatever was lost has to be rebuilt by error correction, so encodeQr ignores the requested level and encodes at H, the highest recovery level, whenever a logo option is present. It records the override in a flag, and the generator shows the level as locked to H whenever a logo is present.

The renderer enforces the same rule from the other side. Asked to draw a logo over a symbol that was encoded at a lower level, it refuses: "A logo overlay requires error correction level H. Re-encode with the logo option set."

Worked example: the same payload with and without a logo

Network LibraryGuest, WPA2, password turn-the-page-7 — a fictional network — builds this payload:

WIFI:T:WPA;S:LibraryGuest;P:turn-the-page-7;;

The WIFI: convention follows the ZXing "Barcode Contents" wiki, the source the builder cites, verified 2026-07-20. WPA2 is emitted as T:WPA because the joining device negotiates the strongest protocol itself — see the WiFi payload guide.

Without a logo, at the default level: version 4, 33×33 modules, error correction Q.

The same payload with a fictional mark placed in the centre. The engine forced error correction to H and recorded the override; the symbol is now version 6, 41×41 modules.

The knockout covers 12% of the symbol area — 14.2 modules a side — and the image inside the pad is 12.2 modules a side. Rendered by the same engine as the generator, for a network that does not exist.

The cap: 16% of the symbol area, decode-tested

The knockout — image plus pad, everything that obscures modules — may cover at most 16% of the symbol area, measured against the symbol excluding its quiet zone. By default it covers 12%. Because the knockout is square, 16% of the area is 40% of the symbol's side, and the default is about 35% of the side — so the default knockout is already close to the cap in side length.

A request above the cap is rejected, not clamped. The engine does not quietly shrink the logo, because a silently shrunk logo would hide the fact that the code had been asked to be unreliable. Its exact message is: "A logo may cover at most 16% of the symbol area — above that, the code stops decoding even on a clean scan."

Why 16% and not something nearer the recovery level? The cap is decode-tested — the round-trip suite renders a symbol with a worst-case logo at that size, rasterizes it, and decodes it with an independent decoder — not derived from the theoretical ~30% EC-H recovery ceiling, which a centred logo exhausts well before 30% of the area. The reason is the shape of the damage: a centred knockout produces burst erasures that consume the correction margin much faster than scattered damage does. So the cap is set by the measured decode margin, and the default is held lower still to leave headroom for print and scan degradation.

Logo geometry at fixed sizes

computeLogoGeometry takes the logo options, the symbol size in modules and the pixel size of one module. It returns the knockout side — the square root of the area ratio times the symbol's side in pixels — and the image side, which is the knockout minus the pad on both sides. When you download a PNG, the exporter sets the module size by dividing the preset's pixel width by the total module count including the 4-module quiet zone on each side, then calls the same function. For the logo example above (41×41 modules), that gives the values below — they are the function's output; the exporter rounds each side to a whole pixel when it draws:

PNG preset Print size at 300 DPI One module Knockout at 12% Knockout at 16%
512 px 1.71 in × 1.71 in (43.3 mm × 43.3 mm) at 300 DPI 10.4 px 148.4 px (12.6 mm), image 127.5 px 171.4 px (14.5 mm), image 150.5 px
1024 px 3.41 in × 3.41 in (86.7 mm × 86.7 mm) at 300 DPI 20.9 px 296.8 px (25.1 mm), image 255.0 px 342.7 px (29.0 mm), image 300.9 px
2048 px 6.83 in × 6.83 in (173.4 mm × 173.4 mm) at 300 DPI 41.8 px 593.6 px (50.3 mm), image 510.0 px 685.5 px (58.0 mm), image 601.9 px

Because the knockout is a fraction of the symbol rather than a fixed pixel count, it is the same number of modules at every preset — 14.2 a side at the default and 16.4 a side at the cap for this symbol — and only the pixels and millimetres change. The print-size column is the engine's readout at 300 DPI, explained in the quiet zone and print size guide.

The logo itself is read on your device and embedded as a data: URL. The engine rejects remote image URLs, so a code with a logo is still a static code: the image is part of the file you download, with no redirect through our servers.

Reading the symbol facts

Every worked example on this site reports the same facts, and the generator reports them for your own code. The version is the grid size the encoder chose for your payload at your level; the module count follows from it; the error correction level is the level actually encoded, after any boost or any logo override; and the print size is the PNG preset divided by 300 DPI. What those facts do not tell you is whether the printed result scans. Error correction helps a damaged code scan, but print size and contrast still matter. Treat a freshly generated code as untested until a real device has scanned it.

Common misreadings

  • "Error correction will cope if I crop the white border." It will not. The 4-module quiet zone is part of the symbol, not damage to be repaired. The renderer draws it into every download and cannot be told to leave it out; cropping it is the most common self-inflicted scan failure.
  • "A strong level makes up for pale or inverted colours." Contrast is not damage either. The style panel computes your colour pair with the WCAG relative-luminance contrast formula, warns below 4.5:1, and warns separately for light-on-dark (inverted) codes, which many scanners reject. See the contrast and inverted codes guide.
  • "H makes any logo safe." The cap, not the level, is the limit. H is already the highest recovery level, so there is no stronger setting to reach for when a logo is too large — the engine rejects the request instead.
  • "The pad around the logo is decoration." The pad is counted in the cap because it obscures modules exactly as the image does. The 16% applies to the whole knockout square.
  • "The code scanned, so the password is right." A scan proves the symbol survived; it says nothing about the payload's truth. A wrong password encodes fine.

Error correction and logo FAQ

Does error correction fix a wrong password or a mistyped link?

No. Error correction lives in the symbol layer: it rebuilds modules that were lost from the printed grid. It has no view of what the payload text means. A WiFi string with a typo in the password encodes without complaint at every level and decodes to exactly that typo. The engine validates format, not truth.

Why can I not pick L, M or Q when a logo is present?

Because the logo destroys modules on purpose, and error correction is the only thing that can rebuild them. The engine encodes at H — the highest recovery level — whenever a logo option is present, ignores the requested level, and records the override. The renderer refuses to draw a logo over a symbol encoded at any lower level.

Why is the logo capped at 16% rather than derived from the ~30% EC-H ceiling?

The 16% cap is decode-tested: the round-trip suite verifies that a worst-case logo at that size still scans. It is not derived from the theoretical ~30% EC-H recovery ceiling, which a centred logo exhausts well before 30% of the area, because a centred knockout produces burst erasures that consume the margin much faster than scattered damage. The default of 12% is held lower still for print and scan degradation.

What happens if I ask for a logo larger than the cap?

The request is rejected, never silently shrunk. The engine's own message is: "A logo may cover at most 16% of the symbol area — above that, the code stops decoding even on a clean scan." A clamped logo would hide the fact that the code had been asked to be unreliable.

Can I crop the white border if my code uses strong error correction?

No. The 4-module quiet zone is part of the symbol, not spare space that error correction can absorb. The renderer draws it into every download and it cannot be disabled; cropping it is the most common self-inflicted scan failure.

Does picking H make my printed code reliable?

It changes how much of the symbol can be lost and still decoded, and it usually changes the symbol itself: more recovery means more codewords, which can step the version up and make every module smaller at the same print size. Error correction helps a damaged code scan, but print size and contrast still matter. Treat a freshly generated code as untested until a real device has scanned it.

Related guides: quiet zone and print size, contrast and inverted codes, and the full guide index. The error-correction and logo rules above are enforced by the same engine that runs in your browser on every generator, from the WiFi code to the contact card; how the encoding is built and verified is on the methodology page.