How big can the logo on a QR code be? We measured it
"30% error correction" is not a logo budget. We punched growing holes in codes at every EC level until a real decoder gave up, and recorded where it broke.
What’s new here: The largest centred patch a QR code tolerated at each error-correction level, measured by punching a growing hole in a real symbol and decoding it with jsQR — 4% at L, 7% at M, 12% at Q, 17% at H — plus two results nobody publishes: scattered damage breaks a code at 4% where one contiguous blob survives 17%, and the same patch over a finder pattern breaks it at 1%.
Every guide to putting a logo on a QR code says the same two things: use error correction level H, and remember it can recover 30% of the code. The second half is repeated so often that it has become a design brief — cover up to 30% — and it is not what the number means.
We went and measured what it does mean.
# What “30% error correction” actually means
A QR code does not store your data as pixels you can lose a few of. It stores codewords: blocks of eight modules, protected by Reed–Solomon error correction. Level H’s headline figure describes roughly how many of those codewords can be reconstructed — and part of that capacity is consumed by the encoding’s own overhead before you touch anything.
Two consequences that matter more than the number:
- Damage is counted per codeword, not per module. Ruining one module ruins the whole codeword it belongs to.
- The finder patterns, timing patterns and format information are not covered by error correction at all. They are how the decoder finds the symbol in the first place, and there is nothing to reconstruct them from.
So “30%” is a fact about the maths and not a permission slip about the picture. Here is the permission slip.
# The measured logo budget, per level
The method, so you can re-run it: encode https://mqr.sh/AB12CD with the
encoder MostlyQR ships (buildMatrix, wrapping the MIT node-qrcode), pinned to
symbol version 3 (29 modules) at every error-correction level so that EC is the
only variable. Render at 8 pixels per module with the full 4-module quiet zone.
White out a centred square, starting at 1% of the symbol’s area and growing 1% at
a time, and hand the pixels to jsQR 1.4.0 after each step. The number recorded
is the last size that decoded before the first failure.
| Error correction | Largest centred white patch that still decoded |
|---|---|
| L | 4% of the symbol’s area |
| M | 7% |
| Q | 12% |
| H | 17% |
Level H is worth roughly four times level L — a real difference, and the reason every guide tells you to use it. It is also nowhere near 30%.
As a rule of thumb from these numbers: at level H, a centred logo occupying about 15% of the code’s area — a square roughly 39% of the code’s width — is inside the measured limit with a little room to spare. The measured limit at level M is 7%, which is a square about 26% of the width, so derate it the same way and you are down to about a fifth.
We re-ran the whole thing at 16 pixels per module and got identical results, which is the expected outcome: this is a property of the symbol, not of the rendering.
# Why a blob is cheaper than confetti
Here is the result we did not expect, and it is the one that explains why centred logos work at all.
Instead of one contiguous patch, we flipped individual data modules at random across the symbol — leaving every finder, timing and alignment pattern untouched — and increased the rate until decoding failed. Same payload, same version, same decoder, five different random seeds:
| Error correction | Max scattered damage (median of 5 runs) |
|---|---|
| L | 1% of 567 data modules |
| M | 3% |
| Q | 3% |
| H | 4% |
(The v3 symbol has 841 modules, of which 567 carry data; the rest are finder, timing, alignment and format patterns, which this test left alone.)
Four percent scattered, against seventeen percent in one blob, at the same level.
The reason is the codeword. A contiguous square of 17% ruins a small number of codewords completely. The same quantity of damage sprinkled evenly ruins a piece of many codewords — and a codeword with one bad module is exactly as broken as a codeword with eight. Error correction counts casualties, not damage.
Which reverses the usual instinct. A single clean shape in one place is the cheapest way to spend your error correction. Speckling, a texture, a half-transparent watermark or a photo filter across the whole code is the most expensive, even when it looks like less damage.
# The three corners you must not cover
Same test, same growing square, moved to two other positions:
| Patch position | Level H breaking point |
|---|---|
| centre | 17% |
| bottom-right corner (over the alignment pattern) | 5% |
| top-left corner (over a finder pattern) | 1% |
At level L the top-left case failed at the very first step — under 1%.
Those corners are structure, not content. The three large concentric squares are the finder patterns, and the smaller one inset near the bottom-right is an alignment pattern; between them the decoder works out where the symbol is, how big it is and how it is rotated or skewed. Cover one and there is no decoding step to reach. Error correction level makes almost no difference, because error correction was never protecting them.
This is why the safe place for a logo is the middle, and why “just make it smaller and put it in a corner” is precisely backwards.
# What a camera adds that this test does not
Everything above is a clean, synthetic, best case:
- One decoder. jsQR is a good, widely used decoder, but a phone’s camera stack is not jsQR. Real readers vary in how aggressively they attempt a damaged code.
- Perfect pixels. No blur, no glare, no perspective, no print. Every one of those reduces the budget.
- A clean white hole. A real logo has colour, edges and antialiasing against the modules underneath. The white plate behind a logo is not decoration — it is what makes the covered area unambiguous.
Treat the table as an upper bound measured under ideal conditions, not a target to design up to. Our own advice is to spend about two-thirds of it.
# Re-running this yourself
Every number above is printed by a script rather than typed into the article:
draft/mostlyqr/seo/benchmarks/logo.js in our repository. It renders the symbol at
each level, grows each patch a percent at a time, runs the scattered-damage
comparison over five seeds, and re-runs the whole thing at a second pixel scale.
It also probes our own validator for the boundary quoted at the end of this piece.
# What to actually do
- Use error-correction level H when there is a logo. It roughly quadruples your budget, and it costs about four extra modules a side on a short link — which is a size decision you can compute rather than guess at.
- Keep the logo centred, and keep it under about 15% of the code’s area. That is a square about 39% of the code’s width, at level H.
- Put it on a solid plate, so the covered area is clean rather than ambiguous.
- Never let anything touch the three corner squares — not the logo, not a frame, not a rounded-corner crop.
- Test the actual print with a bad phone. Every number here says which designs are worth testing. Only the test says whether it works.
If you want the image to be the whole code rather than a patch in the middle of it, that is a different technique with a different budget — we measured photo QR codes separately, and they turn out to spend no error correction at all. And if you just want a code with your logo on it, MostlyQR’s generator forces error correction up to level H the moment a logo is added, keeps the 4-module quiet zone, and defaults the logo to 22% of the code’s width — 4.8% of its area, comfortably inside everything measured above.
One honest note about our own code, since we are publishing the numbers: the guard that refuses an oversized logo uses the nominal 30% recovery figure with a 15% haircut, so it will accept a logo covering about 25% of the area at level H. Our measurement says the real ceiling is nearer 17%. The default nobody changes is safe by a wide margin, but the limit is looser than the evidence supports, and that is a bug in our validator rather than a subtlety of QR codes, and it is on the fix list.
Frequently asked questions
Can a logo cover 30% of a QR code?
No. The "30%" in error-correction level H refers to codewords — blocks of eight modules that Reed-Solomon can reconstruct — and part of that budget is already spent on the encoding itself, not held in reserve for you. In our test, the largest centred square we could white out of a level-H code and still decode was 17% of the symbol's area. At level L it was 4%. Treat 30% as a description of the maths, not as a design allowance.
Which error correction level should I use for a QR code with a logo?
H, if you can afford the size. In our measurement it roughly quadrupled the tolerable centred patch compared with level L (17% against 4%) at the cost of four extra modules per side on a short link. The one caveat is that error correction and a small printed size compete for the same budget — a level-H code with a logo needs to be printed bigger, not smaller.
Why does my QR code stop working when I cover a corner?
Because the corners are not data. The three large squares are finder patterns, the smaller square near the bottom-right is an alignment pattern, and both are how a reader locates and squares up the symbol before it decodes anything. They are not protected by error correction — there is nothing to reconstruct them from. In our test a patch over a finder broke decoding at 1% of the symbol's area, against 17% in the middle.
Does a logo on a QR code need a white border?
In practice yes, and our numbers assume one. We tested by whiting out a square, which is the best case: a clean, high-contrast hole with sharp edges that the decoder can treat as uniformly light. A logo dropped straight onto the modules with no plate behind it produces ambiguous half-dark modules around its edge, which cost more than the same area of clean white.
- Do photo QR codes actually scan? Our decode-test resultsA QR code that is a photograph sounds like it must scan worse. We measured ours with a real decoder — blurred, shrunk, and read the way a reader reads.
- How small can a QR code be printed? The sizes, computedThere is no single minimum size for a QR code. We ran five real payloads through our own encoder for the module counts, then turned those into millimetres.