How small can a QR code be printed? The sizes, computed
There 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.
What’s new here: Symbol version and module count for five real payload types at all four error-correction levels, computed with the encoder MostlyQR ships, then converted to printed millimetres — so the minimum size is derived from your payload instead of quoted as one blanket number.
Every guide to printing QR codes gives you a single number. Two centimetres. One inch. Three centimetres for packaging. Those numbers are guesses dressed up as standards, and they are wrong in both directions: too big for a short link on a business card, dangerously too small for a vCard on a bottle label.
The real answer is a division, and you can do it before you print anything.
# Why there is no single minimum size
A QR code is not a picture that gets smaller. It is a grid of modules — the little squares — and the number of modules is fixed by your payload, not by you. Twenty-one characters of short link needs a 25×25 grid. A vCard with a name, a phone number and an email needs 49×49. Print both at 25mm and you have printed two very different things: in the first, each module is 0.76mm across; in the second, 0.44mm.
The module is what the printer has to hold and the camera has to resolve. So the question “how small can my QR code be” is really “how small can one module be, and how many of them do I have” — and the second half is computable.
# What your payload costs in modules
We ran five payload types through the encoder MostlyQR actually ships
(buildMatrix in our packages/qr, which wraps the MIT node-qrcode library) at
all four error-correction levels. The number after the slash is the grid size —
the modules across one side, not counting the quiet zone.
| Payload | Characters | EC L | EC M | EC Q | EC H |
|---|---|---|---|---|---|
| short link | 21 | v2 / 25 | v2 / 25 | v2 / 25 | v3 / 29 |
| Wi-Fi join (WPA) | 47 | v3 / 29 | v4 / 33 | v4 / 33 | v6 / 41 |
| campaign URL with UTM tags | 87 | v5 / 37 | v6 / 41 | v7 / 45 | v9 / 53 |
| long product URL | 116 | v6 / 41 | v7 / 45 | v9 / 53 | v10 / 57 |
| vCard (name, org, phone, email, URL) | 135 | v6 / 41 | v8 / 49 | v9 / 53 | v11 / 61 |
These are the exact strings, so you can reproduce every row:
https://mqr.sh/AB12CD
WIFI:T:WPA;S:CafeGuest;P:coffee-and-cake-2026;;
https://example.com/spring-menu?utm_source=poster&utm_medium=qr&utm_campaign=spring2026
https://example.com/collections/spring-2026/products/heirloom-tomato-seed-pack-organic?variant=4471029&ref=packaging
BEGIN:VCARD ⏎ VERSION:3.0 ⏎ N:Celar;Max ⏎ FN:Max Celar ⏎ ORG:MostlyQR ⏎
TEL:+3861234567 ⏎ EMAIL:max@mostlyqr.com ⏎ URL:https://mostlyqr.com ⏎ END:VCARD
The spread is the point. Across ordinary, everyday payloads the grid runs from 25 to 61 modules a side — a factor of 2.4 in linear density, before anyone has chosen a paper size.
# If you check this table, check it with the strings
Character count alone will not reproduce those numbers, and it is worth knowing why before you conclude we got them wrong.
A QR encoder does not treat every character as a byte. It picks an encoding mode per run of characters — numeric (3⅓ bits per digit), alphanumeric (5½ bits, but only for digits, uppercase letters and a handful of symbols), or byte (8 bits) — and a good encoder splits one payload into several segments to get the cheapest total. Ours does. Here is what it actually chose:
| Payload | Segments |
|---|---|
| short link | Byte:14 + Alphanumeric:7 |
| Wi-Fi join | Alphanumeric:10 + Byte:37 |
| campaign URL | Byte:83 + Numeric:4 |
| long product URL | Byte:95 + Numeric:7 + Byte:14 |
| vCard | Alphanumeric:11 + Byte:1 + Alphanumeric:11 + Byte:44 + Numeric:10 + Byte:49 + Alphanumeric:9 |
Encode the same character counts as a single byte run instead and you get a different, larger table — v3/29 for the short link at level Q rather than v2/25, and v7/45 for the vCard at level L rather than v6/41. Both tables are correct; they describe different payloads.
Which hands you a free lever, if your short links use an uppercase slug.
https://mqr.sh/AB12CD splits into byte + alphanumeric because the scheme and
host are lowercase. Write the whole URL uppercase — HTTPS://MQR.SH/AB12CD — and
it becomes one 21-character alphanumeric segment: at level L that is v1, 21
modules, a whole version smaller than the mixed-case form. (At levels M and Q
this particular string stays at v2/25 — it is already benefiting from the
alphanumeric tail; the gain shows up at L, and it grows with the length of the
uppercase run.) Scheme and host are case-insensitive so the URL resolves
identically; the path is not, so this only works if your slug is genuinely
uppercase. Ours are.
# The same 25mm, five different codes
Now fix the printed width and read off what each payload leaves you. This is (width ÷ (modules + 8)), the 8 being the mandatory four-module quiet zone on each side:
| Payload | EC M module size at 25mm | EC H module size at 25mm |
|---|---|---|
| short link | 0.76mm | 0.68mm |
| Wi-Fi join | 0.61mm | 0.51mm |
| campaign URL with UTMs | 0.51mm | 0.41mm |
| long product URL | 0.47mm | 0.38mm |
| vCard | 0.44mm | 0.36mm |
Same square on the page. Twice the feature size at one end of the table as the other. A print shop that is comfortable holding 0.5mm cleanly will produce a perfect short-link code and a marginal vCard from the identical layout.
# The minimum width, from the other direction
Most people arrive with the opposite question: not “what do I get at 25mm” but “how small can I go”. Pick a module size your printing process can actually hold — 0.5mm is a common floor for offset and decent digital printing on coated stock — and the width follows:
| Payload | EC M | EC H |
|---|---|---|
| short link | 16.5mm | 18.5mm |
| Wi-Fi join | 20.5mm | 24.5mm |
| campaign URL with UTMs | 24.5mm | 30.5mm |
| long product URL | 26.5mm | 32.5mm |
| vCard | 28.5mm | 34.5mm |
That is the range the internet compresses into “about 2cm”. At the ends of it, 2cm is either 20% larger than you need or 40% smaller than you can get away with.
If your printer will hold 0.33mm — fine screen printing, good digital, no absorbent stock — divide these by 1.5. If you are printing on corrugated card, fabric or anything that spreads ink, go the other way and assume 1mm.
# The biggest lever is the URL, not the printer
Look again at the first table. The short link and the long product URL are the same information — they both take someone to the same page — and one of them is 25 modules while the other is 45.
That is the whole game. Every millimetre of module size you buy by arguing with your printer, you can get for free by shortening what the code encodes. A dynamic short link is 21 characters no matter how long the destination is, and it has a second advantage that matters more on a print run: you can change where it points after the ink is dry, so a code that outlives its landing page does not become a dead end.
For anything that will be printed small — a business card, a label, the corner of a menu — encode a short link and redirect. Encoding the full campaign URL with its UTM tags directly into the code costs you 16 modules a side and buys nothing a redirect cannot do better.
# What error correction costs you in size
Error correction adds redundancy so a damaged or partly covered code still reads. It is not free, and the tables above price it:
- short link, M → H: 25 → 29 modules (+16% linear)
- Wi-Fi join, M → H: 33 → 41 (+24%)
- vCard, M → H: 49 → 61 (+24%)
A quarter more width, for a code that survives being scuffed, printed on packaging, or partly covered by a logo. On a poster, where space is free, take level H and stop thinking about it. On a label where 30mm is all you have, level M with a short link beats level H with a long URL — the shorter payload is carrying more of the robustness than the extra error correction would.
If you are covering the middle with a logo, the trade-off shifts hard toward H, and there is a measured budget for how big the logo can be.
# What this does not measure
Everything above is about the symbol. It is arithmetic over an encoder, it is exact, and you can re-run it. It is also only half of what decides whether a printed code scans:
- We did not test scanning distance with real cameras. The industry rule of thumb — the code’s width should be roughly a tenth of the distance you want to scan it from — is widely cited and we have not verified it. Treat it as a starting point for posters and signage, not as a measurement.
- Ink spread, substrate and contrast are not in the numbers. A 0.4mm module on coated card and a 0.4mm module on corrugated board are not the same module.
- Camera quality varies enormously. A five-year-old phone in a dim restaurant is the real test, and it is the one you should actually run: print the code at the size you intend, on the stock you intend, and scan it with the worst phone in the office.
That last one is not a caveat, it is the advice. The arithmetic tells you which sizes are worth testing. The test tells you whether it works.
# Re-running this yourself
Every table above is printed by a script rather than typed into the article, and
the script is in the repository at draft/mostlyqr/seo/benchmarks/print-size.js.
It runs buildMatrix over the five strings above, dumps the encoding-mode
segments, prints the byte-mode control table next to the real one, and derives the
millimetre figures. If any number here is wrong, that file is where it is wrong.
# The short version
- Encode a short link, not the full URL — it is the only lever that changes the grid instead of the paper.
- Look up your payload’s module count above, or compute it.
- Multiply (modules + 8) by the smallest module your printing process holds cleanly. That is your minimum width. (If you are laying the code into one of our templates, a poster at A3 and a flyer at A4 both leave far more room than any payload here needs.)
- Print it at that size, on the real stock, and scan it with a bad phone.
If you want the code to carry a picture as well as data, the size question gets sharper still — a photograph needs a much denser grid to be visible at all, which we measured separately for photo QR codes.
Frequently asked questions
What is the minimum size for a QR code?
There isn't one number. The code is a grid of modules, and how many modules it needs depends entirely on how much data you put in it. A short link at error-correction level M is 25 modules across; the same code carrying a vCard is 49. Printed at the same width, the second one's modules are almost half the size — so one of them scans at 17mm and the other does not. Work out your module count first, then pick a width.
Does a longer URL make the QR code bigger?
It makes it denser, which amounts to the same thing. In our measurements a 21-character short link encodes to 25 modules at level M, and a 116-character product URL to 45. Print both 25mm wide and the modules are 0.76mm and 0.47mm respectively. Shortening the URL is the cheapest way to make a code printable small — it costs nothing and it is the one lever that changes the grid rather than the paper.
Does higher error correction make the code bigger?
Yes, and by more than most people expect. Going from level M to level H added between 4 and 12 modules per side across all five payloads — for the vCard, 49 modules became 61, which is a 25% increase in the linear size you have to print. Level H is usually worth it if the code will be handled, printed on packaging or partly covered by a logo, but it is a size decision, not a free upgrade.
How much white space does a QR code need around it?
Four modules on every side. That quiet zone is part of the symbol, not decoration — readers use it to find the edges — so it belongs in the arithmetic. Every printed width in this article is computed as (modules + 8) × module size for exactly that reason.
- 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 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.