The first QR code I ever printed went on a poster for a meetup. I made it with the top search result for "free QR code generator," it scanned perfectly on my phone, and I felt very efficient. Three weeks later someone messaged me a screenshot: scanning the poster now opened a page asking them to "upgrade to keep this code active." The poster was still on the wall. The code was dead, and it had never actually contained my URL in the first place — it contained a short link on the generator's domain, which had been forwarding to me only for as long as the free trial lasted.
That is the reason I eventually wrote my own encoder, and the reason this guide exists. Making a QR code is genuinely free. It is a published ISO standard, the math fits in a few hundred lines of code, and there is no part of the format that needs a server, an account or a subscription. What costs money is the middleman that a lot of "free" tools quietly insert, and the mistakes — low contrast, a missing margin, a logo that eats too much of the error correction — that only show up after the print run.
This is the process I actually use now, for posters, business cards, restaurant tables, Wi-Fi signs and the occasional conference slide. It is long because each step has one way to get it wrong that I have personally found. If you only want the short version, there is a checklist at the end.
What a QR code actually is
A QR code is a way of writing text into a square grid of dark and light squares, called modules. That is the whole idea. Masahiro Hara's team at Denso Wave designed it in 1994 to track car parts on assembly lines, which explains two things about it that matter today: it was built to be read fast from any angle, and it was built to survive damage, because a label on a factory floor gets scratched.
Everything a scanner needs is inside the square:
- Three finder patterns — the big nested squares in three corners. They tell the scanner where the code is and which way up it sits.
- Timing patterns — alternating dark and light rows between the finders, so the scanner can count modules even if the image is skewed.
- Alignment patterns — smaller squares that appear from version 2 up, to correct for a code printed on a curved or bent surface.
- Format information — fifteen bits, stored twice, that say which error correction level and which mask the code uses.
- The data itself, plus Reed-Solomon error correction codewords, spread across the rest of the grid.
Two words from the specification come up in every decision you make, so it is worth getting them straight now.
Version is the size of the grid. Version 1 is 21 × 21 modules, and every version after that adds four modules per side, up to version 40 at 177 × 177. More data means a higher version, which means more and smaller modules at the same printed size. Smaller modules are harder to read from a distance and less forgiving of a cheap camera, a shaky hand or bad light.
Error correction level is how much redundancy is stored alongside the data. There are four levels:
| Level | Recovers roughly | When I use it |
|---|---|---|
| L | 7% of the codewords | Screens and clean indoor prints where nothing will cover or scratch the code. |
| M | 15% | My default for anything printed. Good balance of size and resilience. |
| Q | 25% | Outdoor signs, stickers on things people touch, anything that will get dirty. |
| H | 30% | Codes with a logo in the middle, and codes on surfaces I expect to be damaged. |
The trade-off is direct: higher error correction leaves less room for data, so the same content needs a bigger grid. That tension — robustness versus density — is the thread that runs through every step below.
Static versus dynamic, and why "free" is often not
Before you generate anything, you should know which of two very different things you are making, because most generator websites do not say.
A static QR code contains your data directly. If it is a link, the URL is in the pattern. Scan it with any app and you get exactly the text that was encoded. It cannot expire, because nothing exists that could expire it, and nobody can switch it off, because nobody is in the loop.
A dynamic QR code contains a short link on a service's domain. When someone scans it, their phone visits the service, the service logs the scan, and then it redirects to your real address. The service sells this as a feature, and it does have one genuine advantage: the destination can be changed after printing. Everything else about it is a dependency:
- If the service shuts down, changes its pricing or ends your trial, every printed copy breaks at once.
- Every scan goes to them before it goes to you, with the time, the rough location and the device.
- Every scan pays an extra DNS lookup, TLS handshake and redirect on a phone connection, which is the slowest network your visitor will ever be on.
- The printed code is only as trustworthy as a domain you do not control. People are, correctly, learning to look at where a code points before they tap it.
The giveaway is easy to check. Scan a code with your phone's camera and look at the address it previews before you open it. If you see your own domain, it is static. If you see somebody else's short domain, it is dynamic, whatever the website called it. I now do this with every generator before I trust it, including my own.
Here is the part that took me embarrassingly long to realize: you can have the one real advantage of a dynamic code without the middleman. I cover how in step 3.
Step 1: Decide what the code should do
"Make a QR code" is not a specification. The first question is what should happen on the phone when it is scanned, because QR codes can carry more than links, and phones treat each format differently. These are the formats I use, and the exact text each one puts in the code:
# A web page
https://example.com/menu
# Join a Wi-Fi network (WPA/WPA2/WPA3)
WIFI:T:WPA;S:Cafe Guest;P:espresso-2026;;
# Save a contact
BEGIN:VCARD
VERSION:3.0
N:Lovelace;Ada;;;
FN:Ada Lovelace
TEL;TYPE=CELL:+442079460000
EMAIL;TYPE=INTERNET:ada@example.com
END:VCARD
# Start an email
mailto:hello@example.com?subject=Order%20question
# Start a text message
SMSTO:+48600000000:JOIN
# Start a phone call
tel:+48600000000
# Add a calendar event
BEGIN:VEVENT
SUMMARY:Launch party
DTSTART:20261120T180000
DTEND:20261120T220000
LOCATION:Hall B\, 1 Example Street
END:VEVENT
A few things I learned the hard way about these formats:
Wi-Fi codes have escaping rules. Backslash, semicolon, comma, colon and double quote are special and must be preceded by a backslash. A network called Joe's; Guest needs to be written as Joe's\; Guest, or the phone reads the semicolon as the end of the name and the code silently fails to join. A good generator does this for you. A careless one does not, and you only find out when the person at the counter says "it says it can't connect."
Contact cards get big fast. The vCard above with a name, company, title, phone, email and website is about 200 bytes. Add a street address and a note and you are past 330. That is a version 13 code at level M: 69 modules a side. On a business card, that is a lot of very small squares. For cards I usually encode a link to a contact page instead, which keeps the code small and lets me update the details without reprinting.
Calendar and map codes are less consistent across phones. Android handles geo: links well. iPhones are less predictable. If a location code has to work for everyone, encode a link to a map page instead of a geo: URI. Same reasoning for events: test on both platforms before you print hundreds.
Plain text works, but it does nothing. Phones show it as a note the user can copy. That is useful for serial numbers and asset tags, and disappointing on a poster.
Step 2: Make the payload as short as you can
This is the step almost everyone skips, and it has more effect on how well the code scans than any design choice. Every character you encode adds modules. More modules at the same print size means smaller modules, and smaller modules fail first.
I ran a few realistic URLs through the encoder to show how quickly it adds up. These are at error correction M, with the smallest version that fits:
| What was encoded | Characters | Version | Grid |
|---|---|---|---|
https://example.com/menu | 24 | 2 | 25 × 25 |
https://example.com/spring-menu | 31 | 3 | 29 × 29 |
https://example.com/menus/spring-2026/dinner | 44 | 4 | 33 × 33 |
Same, with www. and three UTM parameters | 104 | 6 | 41 × 41 |
Same, plus an fbclid copied from a social click | 168 | 9 | 53 × 53 |
The last row is a real pattern I see constantly. Someone copies the address from their browser after arriving from a Facebook or Google ad, and the URL carries a click identifier (fbclid, gclid, msclkid and friends) that identifies that one click and means nothing on paper. It doubles the size of the code and buys nothing. Delete it.
My rules for a short payload:
- Drop what the server ignores.
www.if your site redirects it anyway, trailing slashes, tracking parameters you did not add yourself. - Use a short path on your own domain.
example.com/morexample.com/qr/menuis better than a deep link, and it sets you up for step 3. - Keep the scheme. It is tempting to encode
example.com/menuwithouthttps://to save eight characters. Some scanners will then treat it as plain text and not offer to open it. Not worth it. - Prefer a link over a big payload. A link to a contact page beats a 330-byte vCard on a business card. A link to a PDF beats trying to encode the PDF's text.
A geeky detail: why uppercase can be smaller
QR codes have several encoding modes. Numeric mode packs three digits into 10 bits. Alphanumeric mode packs two characters into 11 bits, but it only knows 45 characters: digits, uppercase letters, space and $%*+-./:. Everything else, including every lowercase letter, falls back to byte mode at 8 bits per character.
That means HTTPS://EXAMPLE.COM/MENU fits in a version 1 code at level L, while the lowercase version needs version 2. Scheme and hostname are case-insensitive, so uppercasing them is harmless. The path usually is not: most servers treat /MENU and /menu as different pages. I only use this trick when I control the server and have made the short path case-insensitive on purpose.
A good encoder does not make you choose a mode for the whole string. It splits the text into the cheapest mix of segments — a numeric run for a long phone number, byte mode for the lowercase parts — and the difference is noticeable on payloads with long digit sequences. The generator on this site does that split automatically and shows you which segments it chose.
Step 3: Decide whether you will ever need to change it
This is the honest reason dynamic codes exist, and it is a fair one. You print 2,000 table tents for a restaurant, and next season the menu moves to a different URL. A static code pointing at the old URL is now wrong on 2,000 tables.
The fix is not a QR service. It is a redirect on a domain you already own:
- Pick a short, stable path that exists only for the code:
https://example.com/qr/menu. - Encode that path in a static QR code.
- Make the path redirect to wherever the menu lives today.
- When the menu moves, change the redirect. The printed code never changes.
On Apache, this is one line in .htaccess:
# The printed code points here. Change the target, never the path.
Redirect 302 /qr/menu https://example.com/menus/spring-2026/dinner
On nginx:
location = /qr/menu {
return 302 https://example.com/menus/spring-2026/dinner;
}
Notice the 302, not 301. I used a 301 the first time, because "permanent redirects are better for SEO" was stuck in my head. Then I changed the target, and my own phone kept going to the old page for days. Browsers cache 301 responses aggressively, sometimes indefinitely, so a visitor who scanned once may never see your change. A 302 (or 307) is re-checked every time. For a path whose entire purpose is to change, a temporary redirect is the correct one. If you would rather click than type, the .htaccess generator writes the rule for you.
You now have everything a dynamic code offers: an editable destination, scan counts in your own server logs, and no third party. You also own the domain, which means the code keeps working for exactly as long as you want it to.
Counting scans without a middleman
If you want scans in your analytics rather than in a log file, add UTM parameters to the destination, not to the printed path:
Redirect 302 /qr/menu https://example.com/menus/spring-2026/dinner?utm_source=qr&utm_medium=print&utm_campaign=table-tents
The printed code stays short, the tags ride along on the redirect, and the visits show up in whatever analytics you already use, attributed to the campaign. If you have several placements — table tents, the window poster, the receipt — give each one its own path (/qr/menu-t, /qr/menu-w) and you can compare them.
If you are not using a redirect at all, put the UTM parameters directly in the encoded URL. It makes the code bigger (see the table above), so keep the values short.
One caution from experience: tagged URLs are separate URLs to a search engine. Make sure the destination page has a canonical tag pointing at the clean URL, or you may find ?utm_source=qr variants showing up in Search Console.
Step 4: Generate the code
With a short payload and a decision about redirects, generating the code is the easy part. You have three good options, all free, and none of them involve a redirect service.
Option A: in the browser
I built the QR code generator on this site for exactly this workflow, so I will describe it, but the checklist applies to any tool you use. What I look for in a browser-based generator:
- It shows you the exact payload. Not "your link," but the literal text going into the code. If it will not show you, assume it is a short link on their domain.
- It works offline. Load the page, disconnect from the network, and make a code. If it still works, the encoding happens on your machine and nothing you typed was sent anywhere. That matters for Wi-Fi passwords.
- It gives you SVG. A tool that only exports a small PNG is fine for a screen and wrong for print.
- It does not ask for an email address. There is nothing to account for.
On this site's generator: pick the type (link, Wi-Fi, contact and so on), fill in the fields, and the code redraws as you type. Under the preview is a box labeled "Exactly what is encoded," which is the literal payload, plus the version, error correction level, mask and how many bits of the capacity you are using. The notes underneath flag the problems from step 2 — a click identifier in the URL, a link through a known redirect service, plain http:// — before you print them.
Option B: on the command line
For batches, or for anything that belongs in a build script, I use qrencode. It is in every Linux package manager and in Homebrew:
# PNG: 10 pixels per module, 4-module margin, level M
qrencode -o menu.png -s 10 -m 4 -l M "https://example.com/qr/menu"
# SVG for print
qrencode -t SVG -o menu.svg -m 4 -l M "https://example.com/qr/menu"
# Straight into the terminal, handy over SSH
qrencode -t ANSIUTF8 "WIFI:T:WPA;S:Lab;P:not-the-real-one;;"
The terminal output is my favorite party trick: you can show a Wi-Fi code on a server's console and join from your phone without typing a 30-character password.
Option C: in code
When codes are part of an application — tickets, labels, per-customer links — I generate them in code. In Python, segno is the library I reach for. It has no dependencies, writes SVG and PNG, and by default bumps the error correction level whenever that costs nothing extra:
import segno
qr = segno.make("https://example.com/qr/menu", error="m")
print(qr.designator) # version and level, e.g. "2-M"
qr.save("menu.svg", scale=10, border=4)
qr.save("menu.png", scale=10, border=4)
That "boost for free" behavior is worth understanding. The version is chosen by how much data you have; within that version, there is often spare room. Using the spare room for a higher error correction level makes the code tougher at no cost in size. The generator on this site does the same thing and tells you when it happened.
Step 5: Design it without breaking it
This is where most of the failed codes I have seen were broken. A QR code tolerates a surprising amount of styling, but every choice spends from the same budget: the scanner's ability to tell dark from light, find the corners and rebuild what it cannot read.
Contrast comes first
Dark modules on a light background. Aim for a contrast ratio of at least 4:1, and treat anything under 3:1 as broken. Brand colors are fine as long as they are dark: navy, deep green, burgundy, charcoal. Pastels are not. Mid-gray on white looks elegant on a monitor and fails under a restaurant's warm lighting.
Do not invert the code — light modules on a dark background. The standard allows it, and plenty of phone scanners still cannot read it. I learned this from a dark-themed conference slide that worked on my phone and on nobody else's in the room.
Gradients are fine if both ends of the gradient are dark enough against the background. Check the lightest end, not the average.
Keep the quiet zone
The quiet zone is the empty margin around the code. The standard asks for four modules on every side. Scanners use it to find where the code begins, and a code printed edge-to-edge against a busy photo or a patterned tablecloth often fails even when everything else is perfect.
Designers hate the quiet zone because it looks like wasted space. I have lost that argument and regretted it. If the layout cannot afford four modules, two is the absolute minimum, and only on a plain, light background.
Logos: spend the error correction carefully
A logo in the middle works because of error correction: the scanner treats the covered modules as damage and rebuilds them. That means three rules:
- Use level H. It is the only level with enough redundancy to absorb a logo and still survive a scratch or a reflection.
- Keep the logo small. At level H the code can rebuild about 30% of its data. I keep the logo under half of that budget, which in practice means a logo about 20% of the code's width. Bigger logos work on a perfect print and fail on a slightly worn one.
- Never cover the finder patterns. The three corner squares are not data; they are how the scanner finds the code at all. Error correction cannot rebuild them.
Clearing the modules behind the logo, rather than pasting the logo over them, makes for a cleaner edge and avoids half-covered modules that confuse the scanner. The generator here does that by default, and its notes tell you what percentage of the data modules the logo covers and how much of the error correction budget that uses.
Module and corner styles
Rounded modules, dots and custom corner shapes are fine within limits. Squares remain the most robust choice because they put the most ink where the scanner expects it. Dots and gapped tiles cover less area, so they need more size and more contrast to scan equally well. Custom corner markers are usually fine as long as the nested-squares structure (dark ring, light ring, dark center) is still clearly there.
My rule: the smaller the print, the plainer the code. A 2 cm code on a business card gets square modules and no logo. A 30 cm code on a window can afford dots and a logo.
Step 6: Export the right format at the right size
SVG for print, PNG for screens
SVG is vector. It stays perfectly sharp at any size, it is tiny, and it is what a print shop wants. Use it for posters, packaging, signs, business cards and anything going into a design tool like Figma, Illustrator, Affinity or InDesign.
PNG is pixels. Use it for slides, documents, emails and anywhere that does not accept SVG. Never use JPEG for a QR code: its compression smears the sharp edges between modules into gray fringes, which is exactly the information the scanner needs.
Doing the pixel math
For print from a PNG, work out the pixels you need at 300 dots per inch:
pixels = printed width in inches × 300
1 inch (2.5 cm) -> 300 px
2 inches (5 cm) -> 600 px
4 inches (10 cm) -> 1200 px
Then make sure each module is a whole number of pixels. A version 3 code with a 4-module margin is 37 modules wide. At 600 pixels that is 16.2 pixels per module, which means some modules are 16 pixels and some 17, and the edges get anti-aliased into gray. At 592 pixels (16 per module) every edge is crisp. The generator on this site has a "snap to whole pixels" option for exactly this; on the command line, qrencode -s sets pixels per module directly, so it is always whole.
How big should it be printed?
The rule of thumb I use is a 10:1 ratio between scanning distance and code width:
| Where it goes | Typical distance | Minimum width |
|---|---|---|
| Business card, product label | 15–20 cm (6–8 in) | 2 cm (0.8 in) |
| Table tent, flyer, menu | 30–40 cm (12–16 in) | 3–4 cm (1.2–1.6 in) |
| Poster at eye level | 1 m (3 ft) | 10 cm (4 in) |
| Shop window, across a room | 3 m (10 ft) | 30 cm (12 in) |
Treat 2 cm as an absolute floor even for close-range codes, and add size for every complication: a dense code (version 7 and up), a logo, styled modules, a curved surface. These numbers assume a short payload. A version 9 code with its margin is 61 modules wide against 37 for version 3, so it needs to be about two-thirds wider to keep the same module size.
Step 7: Test it like you mean it
"It scans on my phone" is the test that let my first poster through. My phone is recent, I hold it steady, I know where to point it, and I was testing a code on a bright monitor. None of that describes the person standing in front of your poster.
The test I run now, every time, before anything goes to print:
- Read the payload. Scan with the built-in camera and look at the address it previews before you open it. Is it your domain, your exact URL, no stray parameters? This takes two seconds and catches the redirect-service problem immediately.
- Test on both platforms. At least one iPhone and one Android phone, using the built-in camera app, not a third-party scanner app you have installed and the public has not.
- Test at the real size. Print it on a home printer at the final size, or set the display to that physical size. A code that scans at full screen may fail at 2 cm.
- Test at the real distance. Stand where the person will stand. For a window poster, that is across the room, not at arm's length.
- Test in bad conditions. Dim light, an angle, a bit of glare. If it is going on a glossy surface, test the gloss.
- Test the destination. Open the page on mobile data, not your office Wi-Fi. Make sure it loads fast and is not behind a login or a cookie wall that eats the visit.
For files rather than prints, I also run an automated check. zbarimg (part of the zbar tools) decodes an image file and prints what it finds, which is perfect for a build script:
$ zbarimg --quiet menu.png
QR-Code:https://example.com/qr/menu
If the output does not match the payload exactly, the build fails. In browsers that ship a built-in QR reader, the generator on this site runs the same check on every change and tells you whether the image decodes back to the exact payload. Where the browser has no reader, it says so instead of pretending.
Step 8: Put it where it can be scanned
A perfect code in the wrong place still fails. Things I now check on site, not on screen:
- Height and angle. A code at knee height on a window gets scanned by nobody. Eye level, facing the viewer.
- Surface. Glossy lamination reflects ceiling lights straight into the camera. Matte finishes scan much better. Curved surfaces like bottles and cups distort the grid: keep the code small relative to the curve, or put it on a flat label.
- Moving targets. Codes on vehicles, rolling screens and slides that change every ten seconds need to be big and simple. Nobody gets a second chance at a bus.
- Connectivity. A code in a basement venue or a stadium is pointing at a web page on a network that may not exist there. For Wi-Fi codes this is fine; for links, consider what happens when the page will not load.
- Context. Put one line of text next to the code that says what it does: "Scan for the menu," "Scan to join Wi-Fi." People are, reasonably, wary of scanning unlabeled codes.
Security: the other side of the code
Because a QR code hides its destination until it is scanned, it is a good tool for scammers. The pattern is common enough that the US Federal Trade Commission published a consumer alert about QR code scams in December 2023: fraudsters put stickers over legitimate codes on parking meters and posters, or send codes by text message, and the destination is a fake payment page.
Two things follow for anyone printing codes:
- Make yours easy to verify. A code that points at your own, recognizable domain is one a cautious person can check in the preview before opening. A code that points at a random short domain looks exactly like the scam. This is one more reason I avoid redirect services.
- Protect codes in public places. Print them into the material rather than as stickers where you can, and check outdoor codes periodically for something glued over the top.
And for Wi-Fi codes in particular: the printed code is the password. Anyone who can photograph it can join. Use a guest network that is isolated from your printers, file shares and point-of-sale terminals.
Troubleshooting: when a code will not scan
These are the failures I have actually run into, roughly in order of how often:
| Symptom | Likely cause | Fix |
|---|---|---|
| Scans on some phones, not others | Low contrast, inverted colors, or a dense code | Darker modules on white; shorten the payload |
| Scans close up, not from a distance | Modules too small for the distance | Print larger (10:1 rule) or lower the version with a shorter payload |
| Scans on screen, not in print | JPEG artifacts, blurry upscaled PNG, or ink spread on cheap paper | Send the printer an SVG |
| Phone finds the code but does nothing | Payload is not a recognized format (missing https://, broken Wi-Fi escaping) | Read the raw payload and fix the format |
| Fails only with the logo | Logo too large, error correction too low, or a finder pattern covered | Level H, logo around 20% of the width, corners untouched |
| Stopped working months later | The code was a dynamic link and the service expired it | Reprint as a static code pointing at your own domain |
| Opens an old page after you changed it | Browser cached a 301 redirect | Use 302 or 307 for redirects you intend to change |
| Fails on a bottle or a cup | Curvature distorts the grid | Smaller code on a flatter area, or a flat label |
The checklist
This is the list I keep next to my desk. It is everything above in the order I do it:
- Decide the action: link, Wi-Fi, contact, email, SMS, call, event.
- Shorten the payload: own domain, short path, no click identifiers.
- If it might change, encode a path on your own domain and redirect it with a 302.
- Generate a static code with a tool that shows the exact payload. Level M by default, H with a logo.
- Dark on light, contrast at least 4:1, four-module quiet zone.
- Logo at most about 20% of the width, finder patterns untouched.
- SVG for print, PNG at whole pixels per module for screens. Never JPEG.
- Size it at roughly one tenth of the scanning distance, never under 2 cm.
- Test on an iPhone and an Android phone, at real size, real distance and bad light.
- Add a one-line label that says what the code does.
None of this costs anything. The QR code generator runs entirely in your browser, shows you every byte it encodes and never routes a scan through us. When you scan the result, the address in your phone's preview is yours — which, after that first poster, is the only kind of QR code I am willing to print.
Frequently asked questions
Do free QR codes expire?
A static QR code cannot expire; the data is in the pattern. If a "free" code stopped working, it was a dynamic code — a short link on the generator's domain — and the service turned the link off. Scan any code and check the address preview to see which kind you have.
Can I use a QR code commercially?
Yes. The format is an open ISO standard (ISO/IEC 18004), and Denso Wave states that the codes can be used freely without a license or fee. Only the word "QR Code" itself is a registered trademark of Denso Wave.
How much data can a QR code hold?
The largest code, version 40 at error correction level L, holds 7,089 digits, 4,296 alphanumeric characters or 2,953 bytes. In practice, you should never get near that: anything above a few hundred characters produces a code that is hard to scan. If you have that much to say, encode a link to a page.
Can I track scans on a static code?
Not from the code itself, but you do not need to. Point the code at a path on your own domain and count requests in your server logs, or add UTM parameters and count visits in your analytics. That measures people who actually reached the page, which is the number that matters.
Can I change where a static code points?
Not the code, but you can change what it points at. Encode a short path on your own domain and make that path a 302 redirect; changing the redirect changes the destination for every printed copy.
Why does my QR code look so dense?
Because it holds a lot of data, or uses high error correction, or both. Shorten the URL, remove tracking parameters you did not add, and drop to level M if there is no logo. Each step can take several versions off the size.
Published · Technical SEO Web Development