← Blog

What makes a good QR code (that people actually scan)

30 May 2026

You've printed the codes and put them up, and the scans aren't coming. So you ask: why aren't people scanning them? The code printed fine. The link worked when you tried it on your own phone.

There's rarely one big reason. More often it's one of four small ones. The code is crammed. It can't cope with where it's been stuck. It's too small for the cameras aimed at it. Or nobody can see a reason to bother.

Each of those is fixable. Below, every section puts a code that struggles next to one that doesn't. They're real codes, so point your phone at them.

1. Keep the payload short

A QR code stores your data as a grid of little black and white squares, called modules. Cram in more data and it needs more of them, so the grid gets busier. Same print size, smaller squares, and now a camera has to pick out finer detail to read it. That's the gap between an instant scan and someone waggling their phone at a wall.

✗ Avoid: a 103-character link full of tracking tags. Version 6, 41×41 modules.
✓ Better: example.com/spring. Version 2, 25×25. Same print size, much bigger squares.

The fix is simple: shorten what the code carries. Nothing else helps as much.

None of this assumes a web address, by the way. A code doesn't have to hold a link at all: it can carry WiFi credentials, a contact card, or a phone number. Whatever's inside, shorter wins.

The capitals trick

QR codes have a compact mode for a limited alphabet: digits, CAPITAL letters, space, and $ % * + - . / :. Text that sticks to that alphabet packs into about 5.5 bits a character instead of 8. Lowercase letters aren't in it, so an ordinary lowercase URL falls back to the roomier byte mode.

The scheme and domain of a web address don't care about case. HTTPS://EXAMPLE.COM goes to the same place as https://example.com. Write the whole link in capitals and the same address fits in a smaller code:

✗ Avoid: https://example.com/menu/table-12. Byte mode, version 3, 29×29.
✓ Better: HTTPS://EXAMPLE.COM/MENU/TABLE-12. Compact mode, version 2, 25×25.

The catch is the path. Whether /MENU/TABLE-12 and /menu/table-12 are the same page is up to your server, and plenty treat them as different. Open the capitalised link in a browser before you print, or have your site or link shortener accept either case. Characters outside the alphabet, like ?, =, & and _, force those parts of the code back into byte mode, so the trick pays off best on clean links. BulkQR's encoder switches to the compact mode on its own whenever your data fits, so all you have to do is supply the capitals.

2. Match error correction to the environment

QR codes carry a bit of built-in backup called error correction. It lets a scanner rebuild the data when part of the code is missing or scuffed. There are four levels:

More isn't automatically better. Every extra bit of backup adds modules, and you've just seen what that does. Pick the level for where the code actually lives:

LOGO
✗ Avoid: a logo over a level L code. There's too little backup to rebuild what's hidden, and it won't scan.
LOGO
✓ Better: the same link and logo at level H. The code is denser, but it reads.

Putting a code somewhere it'll get handled, scuffed, or wrapped round a bottle? The extra headroom of Q or H costs you almost nothing. Same thinking behind codes that scan at a retail till and labels printed small on a gallery wall.

3. Size it for the worst camera that'll scan it

Rough rule: make the code about a tenth of the distance people scan it from. Scanning from 40 cm away? That's a 4 cm code, minimum. A poster read from two metres wants a 20 cm code, far bigger than most people print.

Then build for the worst case, not your own handset:

The recipe never changes: short payload, decent size, strong contrast. The code that gets waved at and given up on is the small, dense, washed-out one.

And leave the quiet zone alone: the empty margin round the code. Scanners use it to find the edges. Pack text or artwork right up against it and you'll get failures that look like nothing's wrong. Keep a clear border, roughly four modules wide.

SPRING SALE
✗ Avoid: artwork right up to the edge of the code. Our test decoder couldn't find it.
SPRING SALE
✓ Better: the same code and artwork, with a four-module margin between them.

4. Give people a reason to scan

A bare code tells you nothing. People won't lift their phone unless they know what's coming. The codes that pull their weight sit next to a few words: a verb and a payoff:

✗ Avoid: a bare code. It scans perfectly, if anyone bothers to try.
Scan for the menu
✓ Better: four words tell people what they'll get before they lift their phone.

Keep it short. Put it right by the code. Make the promise specific. "Scan me" is not a reason. "Scan for the audio guide" is.

The rest of the checklist

Those four do most of the work. A handful of details stop a good code being wrecked by something silly:

Colour is where contrast usually slips:

✗ Avoid: pale lilac on white. Crisp on a screen, but add glare, dim light or a cheap camera and it's the first to fail.
✓ Better: deep indigo on white. Still on brand, and it reads like black.

Getting it right at scale

One code, and all this fits in your head. The trouble starts at a hundred: a code per SKU, ticket, or table, each following the same rules.

That's what BulkQR is for. Set the design once (size, error correction, colour, a logo) and it lands on the whole batch. Up to 100 codes are made right in your browser, so nothing's uploaded, and you download them as a ZIP or lay them out as printable labels. Every code inherits the same sane defaults, so you're not re-checking contrast a hundred times over.

Nail the four, test one on a cheap phone, and let the rest follow.

Ready to make some? Generate your first batch. No account needed.