Skip to content

Tracking

Tracking printed QR codes with UTM parameters

How to tag a printed QR code so scans show up as something other than direct traffic, what the tags cost in module count, and a naming scheme that survives.

by Kyllian 6 min read

Four thousand people scanned the campaign, the site had a good month, and the analytics say “direct / none”. There is no way to tell the poster in the station from the table tent in the cafe, because a camera app opening a link sends no referrer at all. The browser has no previous page to report, so every scan lands in the same bucket as a typed URL.

Query-string tags are the only signal that crosses from paper into the browser.

The five parameters

ParameterCarriesOffline example
utm_sourceWhere the visit came fromposter-station-north
utm_mediumThe kind of channelqr
utm_campaignWhich campaign or print runautumn-menu-2026
utm_contentWhich variant of the creativea4-poster
utm_termKeyword; paid search onlyUsually empty offline

They are ordinary query-string parameters, so ?utm_source=…&utm_medium=… on the end of your URL. Analytics tools read them and attribute the session. GA4 also accepts a sixth, utm_id, for pairing a visit with a cost record; for print you can normally ignore it.

Conventions that hold up offline

The digital conventions map badly onto physical objects, so most teams settle on the same three rules.

utm_medium is the constant. Use qr or offline and never vary it. Its job is to let you pull every scan across every campaign into one row. Pick one of the two and write it into whatever document your agency reads, because a report split between qr, QR and offline is three reports.

utm_source names the physical placement. Not “print”, not “poster” — the specific thing in the specific place. poster-station-north and poster-station-south are different sources because they answer different questions. This is the field that earns its keep.

utm_campaign is the print run. One campaign value per batch of artwork sent to the printer. When you reprint in March with a new offer, that is a new campaign, even if the poster looks identical.

Lower case throughout, hyphens rather than spaces or underscores, and no punctuation that needs URL-encoding. Analytics tools treat Poster and poster as two different values, and a space becomes %20 inside the code, which costs modules and looks broken when anyone reads the URL aloud.

A naming scheme

Placementutm_sourceutm_mediumutm_content
A4 poster, north entranceposter-station-northqra4-poster
A4 poster, south entranceposter-station-southqra4-poster
Table tent, cafemenu-table-tentqrtent-v2
Sticker on the windowwindow-stickerqrvinyl-120mm
Printed on the boxpackaging-boxqrsleeve-back
Leaflet in the parcelparcel-insertqra6-flyer
Business cardbusiness-cardqrcard-2026
Exhibition standstand-hall7-b12qrbanner-3m

utm_campaign stays the same across every row for one print run — autumn-menu-2026 — which is what makes the rows comparable.

Tag before you print

This is the part that gets people. The tagged URL is what the symbol encodes. There is no server in the middle to change your mind at, and no code generated here expires or redirects — the link you typed is the link the code contains, permanently.

So build the URL first, tag it, then generate. The generator has a UTM section in the QR panel that appends only the fields you fill in and encodes the assembled URL, which at least removes the risk of a typo between the spreadsheet and the artwork. Whether a code can be re-pointed after printing is the whole subject of static versus dynamic codes; with a static code the answer is no.

What the tags cost in modules

Tags are not free. Take a menu link:

https://oakleaf.example/menu

Twenty-eight characters, version 3 at error correction M — 29 modules across. Now tag it properly:

https://oakleaf.example/menu?utm_source=table-tent&utm_medium=qr&utm_campaign=autumn-menu-2026&utm_content=table-tent

That is 117 characters and a version 7 symbol: 45 modules. Four versions up, and about 55% more modules per side. Add utm_term and a longer source name and you are at 144 characters, version 8, 49 modules.

Module count is what sets minimum print size, so:

PayloadModulesFloor at 0.4 mmModule size if printed at 2 cm
Plain URL291.2 cm0.69 mm
Four UTM tags451.8 cm0.44 mm
Five UTM tags492.0 cm0.41 mm

At a fixed 2 cm — a table tent, a business card, the back of a pack — a fully tagged URL puts each module within a whisker of the 0.4 mm print floor described in the print size guide. It will still scan on a good phone in good light. It will fail on a cheap one in a dim room, which is exactly the situation printed codes live in.

On a poster read from two metres you need 20 cm of code anyway, so the extra density is irrelevant. On anything small, it is the whole problem.

The short redirect

If the tagged URL is too dense for the space it has, encode a short path on a domain you own and attach the tags on the far side of the redirect: the symbol stays small, the analytics tool still receives the parameters, and the visitor still lands on the destination. That whole trade — the module counts, the 302-not-301 detail, and the conditions under which renting a redirect is the wrong call — is the subject of static versus dynamic QR codes.

The part that belongs here is the tagging discipline it demands. Keep a record mapping each short path to its placement, because oakl.ea/m2 tells nobody anything six months later, and give each placement its own path so the per-placement breakdown survives. If you cannot commit to running the redirect for as long as the print exists, print the long tagged URL and print the code bigger. A dead redirect is worse than a dense code.

Reading the result

In GA4, Reports → Acquisition → Traffic acquisition, then change the dimension to Session source / medium or Session campaign. Your rows appear as poster-station-north / qr.

One warning: GA4’s default channel grouping does not know what qr is, so those sessions land in Unassigned rather than a named channel. offline behaves the same way. Either accept that and work from the source/medium report, or create a custom channel group in Admin that maps utm_medium = qr to a channel of your own. Set it up before the campaign starts, so nobody spends the first week reporting that the posters produced nothing.

Cookieless tools handle this more simply. Umami, Plausible and Fathom all read the utm_ parameters off the pageview and list them in a campaigns breakdown with no cookie and no consent banner, because the attribution is attached to the pageview rather than to a person followed over time. For counting scans per placement — which is the actual question — that is enough, and it is why this site’s own pages use a cookieless tool.

Whatever you use, check one thing on the day the print arrives: scan the real object with a real phone and confirm the tagged hit appears in the live view. The most expensive attribution bug is the one discovered after five thousand menus have been laminated. The restaurant menu guide covers the rest of that pre-launch check.

Frequently asked

Why do QR code scans show up as direct traffic?
Because a camera app opening a link sends no referrer. The browser has no previous page to report, so analytics files the visit under direct or none alongside typed URLs and bookmarks. Query-string tags are the only signal that survives the jump from paper to browser, which is why an untagged printed code is effectively untrackable.
What should utm_source be for a printed QR code?
The physical placement, not the medium. Values like poster-station-north, menu-table-tent or packaging-box tell you which piece of print earned the scan. Keep utm_medium as the constant qr or offline, and reserve utm_campaign for the print run so that two placements in one campaign stay comparable.
Do UTM parameters make a QR code harder to scan?
They make it denser. A 28-character URL encodes as a 29-module symbol; the same link with four UTM tags runs to 117 characters and 45 modules. At a fixed printed size every module is then about a third smaller, so either print the code larger or put a short redirect on your own domain in front of it.
Can I change the UTM tags on a code after it is printed?
Not if the code encodes the destination directly, which is the normal case. The tagged URL is what the symbol contains, so it is fixed at print time. Encoding a short redirect on a domain you control is the only way to keep the tags editable after the ink is dry.

Make the code this guide describes

The generator runs in your browser — add a logo, pick colours, tag the link with UTM parameters and download a PNG or SVG. No account, no watermark.

Keep reading

← All guides