You do not need a QR company’s scan counter to know whether a poster worked. You need the visit to arrive with its origin written on it, and you need a definition of “worked” that is not the scan itself. UTMs are the boring tool that does the first part. The booking, the basket, the call, the signup does the second.
A scan that never paints the page is invisible to this method. That is fine. You cannot take money from a phone that gave up on the load.
Name the placement in the URL
Build the landing page URL, then add the parameters before you generate the code. The symbol stores whatever you paste. If you add UTMs later, you are printing a different code.
| Parameter | Use it for | Example |
|---|---|---|
utm_source | The placement, specific enough to act on | high_street_window, receipt, bag_insert |
utm_medium | The channel. Keep this stable | qr |
utm_campaign | The effort, shared across placements | autumn_tasting_2026 |
utm_content | A creative variant, if you are testing two lines | book_a_table vs see_the_menu |
A window poster and a till card for the same tasting:
https://northwind.example/book?utm_source=high_street_window&utm_medium=qr&utm_campaign=autumn_tasting_2026 https://northwind.example/book?utm_source=till_card&utm_medium=qr&utm_campaign=autumn_tasting_2026
Same page, two codes, two rows in the report. Lowercase, underscores, no spaces. Spaces become %20 and you will spell them two different ways by Friday. Decide the names in a sheet before anyone opens the generator: date, placement, full URL, file name of the SVG, where it was sent. That sheet is the tracking system. The QR code is just a copy of one cell.
GA4 will file “qr” under Unassigned
This is the bug that makes a careful campaign look like a failure. GA4’s default channel group understands mediums such as cpc, organic, email, referral. It does not understand qr. Those sessions still exist. Filter the traffic acquisition report by session source / medium and you will see till_card / qr. Look only at the default channel grouping and the same visits sit in Unassigned, where nobody thinks to look.
Two ways to live with that.
- Report on session source and medium, and ignore the channel column for this campaign. Fast, and you can do it today.
- In Admin, under Data display, copy the default channel group and add a channel named QR whose rule is medium exactly matching
qr. Put that rule above the generic ones. New data picks it up. Historical data does not get refiled just because you wished it would. Check the current GA4 UI labels when you do this. They move the menu every so often and the idea stays the same.
Do not “fix” it by setting utm_medium=email or social so the traffic looks classified. You will spend the next quarter explaining why the newsletter gained 400 sessions from a shop window.
Sessions from till_card / qr are arrivals. The number you take to a meeting is completed bookings, or whatever you tagged as the conversion, divided by those sessions. A poster that sends 80 people who book beats a poster that sends 600 who bounce.
Redirects eat query strings
If you use a short path so the code stays easy to print (the setup in static versus dynamic), the redirect has to keep the parameters. Print this:
https://northwind.example/q/book?utm_source=till_card&utm_medium=qr&utm_campaign=autumn_tasting_2026
The server should send the phone to /book with that same query intact. Test it. Scan the proof, or paste the short URL in a phone browser, and read the address bar after the hop. If the UTMs vanished, your redirect is hard-coding a clean destination. Every placement will report as direct, or as nothing, and the codes were not the problem.
Long query strings make denser symbols. If the card is small, shorten the names (utm_source=till) before you shrink the artwork, and read the size limits. A tracking URL that does not scan is a perfect report on zero people.
Ways the numbers get dirty
- One code everywhere. You learn that QR exists. You do not learn whether the bag insert is worth printing.
- UTMs on buttons inside the site. A “Book” button that carries its own
utm_mediumoverwrites the session’s origin in a lot of setups. Internal links stay clean. The QR code is the only place those parameters live. - A different campaign name per poster. Then you cannot see the tasting as one effort. Campaign is the effort. Source is the placement. Do not swap them.
- Tagging the homepage when the homepage’s job is to offer six next steps. Send the code to the booking page, or the menu, or the form. The campaign guide is picky about this for a reason. Analytics cannot repair a vague destination.
- Comparing a vendor’s “scans” to GA4 sessions. They are different events. Scans count the camera. Sessions count a page that loaded and ran your tag. Quote one of them and say which.
What to pull after two weeks
Give it long enough for the posters to actually be up, then look at a short table:
- Sessions by
utm_source, medium exactlyqr, campaign fixed. - Engaged sessions, so you can see the window traffic that immediately leaves.
- The conversion you named before the print run, by the same source.
- Conversion rate, so a high-traffic placement with no bookings cannot hide behind volume.
If one placement is a zero, check the physical world before you blame the link. Wrong file sent to the printer, code too small, glare, or the short URL 404ing. A zero in analytics is often a production fault. Scan the live piece with your phone before you rewrite the offer.
Questions from the reporting side
How do I track QR codes without a paid QR platform?
Put UTM parameters on a URL you own, one variant per placement, and read sessions plus the real conversion in your analytics. You will not get a scan that failed to load. You will get the visits that could have turned into something.
Why does GA4 show my QR traffic as Unassigned?
Medium qr is not in the default channel rules. The sessions are under that source and medium. Add a channel that matches medium qr, or ignore the channel column and report on source / medium directly.
Should every poster use the same QR code?
Only if you have no intention of comparing them. Same landing page, different utm_source, separate exports. Write the file names down next to the URLs.
Do UTMs make the QR code harder to scan?
They make it denser. On a poster you will not notice. On a small card, a long query can push the modules under a reliable size. Shorten the parameter values or print larger, then proof it.
Generate one code per row in the sheet.
Paste the tagged URL, export SVG, and label the file with the same source name you are about to trust in a report.
Create your QR code