Free tier, no card requiredDynamic QR codes that update after printGDPR-compliant scan analyticsBuilt for agencies, freelancers & in-house teamsFree tier, no card requiredDynamic QR codes that update after printGDPR-compliant scan analyticsBuilt for agencies, freelancers & in-house teamsFree tier, no card requiredDynamic QR codes that update after printGDPR-compliant scan analyticsBuilt for agencies, freelancers & in-house teamsFree tier, no card requiredDynamic QR codes that update after printGDPR-compliant scan analyticsBuilt for agencies, freelancers & in-house teams
All posts
A QR code with a UTM tag, its scan routed into a dedicated QR channel in Google Analytics 4 instead of the catch-all.
How-to

QR codes in Google Analytics 4: why scans show as "direct" (and the UTM fix)

Scan a QR code, open Google Analytics 4, and the traffic shows as 'direct' or 'Unassigned'. Here is why GA4 mis-buckets QR scans, the exact UTM tags and channel group that fix it, and how a dynamic redirect keeps tags editable after printing.

ScanKit

ScanKit · Organization

· 15 min read

You print 5,000 flyers for a client, each carrying a QR code that points at a campaign landing page. The flyers go out, scans start rolling in, and a week later the client asks the obvious question: how many of the people who scanned the flyer actually reached the site? You open Google Analytics 4, and the answer is either useless or actively misleading. The QR traffic sits under "Direct", as if those visitors typed the URL from memory, or it has disappeared into a bucket called "Unassigned".

This is the single most common way QR reporting goes wrong, and it is not a fault in Google Analytics. GA4 was simply never told that the visit came from a flyer. QR scans arrive without the breadcrumbs GA4 normally uses to credit a source, so unless you add those breadcrumbs yourself, the platform does the only thing it can and calls the traffic direct. The fix is a small, precise piece of campaign tagging, plus one setting that most guides never mention. This post covers both, and it pairs naturally with our guide on how to track a print campaign with QR codes and the deeper QR code analytics breakdown.

Why QR scans show up as "direct" (or "Unassigned")

GA4 works out where a visit came from in one of two ways. It reads the referrer, meaning the site the visitor was on immediately before, or it reads campaign parameters that you have added to the destination URL. A QR scan usually gives it neither. When someone points a phone camera at a code, the browser that opens often sends no referrer at all, so GA4 has no site to credit. With no referrer and no campaign tags, GA4 records the session source as (direct) and the medium as (none), and its default rules file that under the Direct channel. By Google's own definition, the Direct channel is exactly the case where source matches (direct) and medium is one of (not set) or (none). That is your QR traffic, mislabelled as people who arrived out of thin air.

The instinctive fix is to tag the URL with something like utm_medium=qr. That does solve the direct problem, because source and medium now populate correctly. But it quietly creates a second problem. GA4 sorts traffic into channels using a fixed set of rules, and not one of them recognises a medium called qr. The defaults look for organic, cpc, email, affiliate, referral, audio and a handful of others, matched character for character. A medium of qr matches none of them, so GA4 falls back to its catch-all and labels the traffic Unassigned. In other words, tagging with utm_medium=qr fixes the "direct" problem and creates an "Unassigned" problem, because qr is a value you invented and GA4's defaults have never heard of it.

Both halves are fixable, and the rest of this guide does exactly that: tag the URL properly so the data is correct, then teach GA4 what your QR medium means so it stops landing in Unassigned.

Why the referrer cannot rescue you on mobile

It is tempting to assume the phone will pass along enough information to identify the source. It usually will not. GA4's referral and organic attribution depend on the HTTP referrer header, and native camera apps and in-app browsers frequently send no referrer, or strip it to nothing, for privacy reasons. When the referrer is missing, GA4 has nothing to compare against its lists of known sites, so the visit defaults to direct. This is the heart of why offline tracking differs from web tracking: you cannot lean on the referrer, so you have to put the source information inside the destination URL itself. That is precisely what campaign tags do, and why they survive a scan when a referrer does not.

The anatomy of a tagged QR destination URL

Campaign tags, almost always called UTM parameters, are key-value pairs you append to a URL after a ?. Google's guidance is to always include three of them: utm_source, utm_medium and utm_campaign. Source is where the visit came from, medium is the type of channel, and campaign is the initiative it belongs to. For a QR code on a client's flyer, a clean tagged destination looks like this:

https://clientsite.com/offer?utm_source=flyer&utm_medium=qr&utm_campaign=acme_spring

Diagram of a tagged QR destination URL split into numbered parts: the base URL, utm_source, utm_medium, utm_campaign and utm_content.
The anatomy of a tagged QR destination URL: the base page plus utm_source, utm_medium, utm_campaign and utm_content.

The numbered parts of that URL break down as follows:

  1. The base destination, the page the visitor should actually land on. Everything after it is tracking metadata that GA4 reads and then hides from the visitor.
  2. utm_source, the specific placement or asset. Use the physical thing the code lives on, such as flyer, poster or packaging_insert, so you can tell placements apart later.
  3. utm_medium, the channel type. For QR this is the value you standardise on, most commonly qr.
  4. utm_campaign, the client initiative the code belongs to, for example acme_spring. Keep one name per campaign across every placement so the report adds up.
  5. utm_content, an optional tag to tell two versions of the same placement apart, such as doordrop_a against doordrop_b in an A/B test.

Two more parameters are worth knowing. utm_id lets you attach a campaign identifier that ties the traffic back to a record in your own systems, and utm_term carries a keyword for paid search, which rarely applies to a printed code. Note that utm_creative_format and utm_marketing_tactic exist in Google's specification but are not surfaced in GA4 reports, so do not promise a client a column for them.

One detail trips up more campaigns than any other: parameter values are case sensitive. To GA4, qr and QR are two different mediums and will split your traffic into two rows that never reconcile. Pick one casing, keep it lowercase, avoid spaces, and write your taxonomy down so everyone on the team tags codes the same way.

Choosing utm_medium for QR: qr, offline, or print?

There is a long-running argument about whether the medium for a scanned code should be qr, offline or print. The honest answer is that it barely matters which word you choose, because none of the three maps to a GA4 default channel. Whatever you pick, GA4 parks it in Unassigned until you tell it otherwise, so choose on clarity rather than on some imagined built-in support.

My recommendation is to make utm_medium=qr your standard, because it describes the actual mechanism, a scanned code, rather than the broad family of print. Then carry the surface detail in utm_source: flyer, poster, packaging_insert, tradeshow_banner, table_tent. That split gives you a single, consistent medium you can group on, and a source dimension rich enough to compare individual placements. If you would rather reserve qr for something else, offline is a defensible alternative, but commit to one and document it. The fastest way to ruin a quarter of reporting is to let three people tag the same campaign as qr, QR and print.

Teach GA4 your QR channel with a custom channel group

Here is the step almost every other guide skips. To stop your QR traffic landing in Unassigned, create a custom channel group in GA4 that recognises your medium. In Admin, under Data display, open Channel groups. You will need Editor access or above at the property level. You cannot edit the default group, so copy it, then add a new rule near the top: set the channel name to QR with a condition that session medium exactly matches qr. Save it, and from then on every code tagged utm_medium=qr reports under a clean QR channel instead of the catch-all.

Two things make this better than it first sounds. Custom channel groups apply retroactively, so GA4 reprocesses your historical data into the new grouping rather than counting only from today. And the limits are generous enough for agency work: a standard property allows two custom channel groups alongside the default, and a Google Analytics 360 property allows five. One QR channel rule, defined once per client property, and the Unassigned problem is gone for good.

Make sure the tags actually arrive: redirects and shorteners

Tagging the URL only helps if the tags survive the trip to the landing page, and this is where dynamic QR codes need a careful eye. A redirect carries your query string only if the redirect target includes it. A 301 or 302 response sends the browser to whatever address sits in its Location header and nothing more; it does not automatically reattach the original ?utm_... string. A well-built dynamic code forwards the visitor straight to your full tagged URL, so the parameters arrive intact. A sloppy one, or a third-party link shortener stacked on top, can drop or overwrite them, and each extra hop is another chance for the tags to fall off.

This is also where a dynamic code earns its keep. With ScanKit, the printed code encodes a short link such as scankit.app/r/your-slug, and the scan is redirected to whatever destination URL you have set. Put the complete, UTM-tagged URL in that destination field and every scanner lands on it with the tags in place. Because the destination is editable, a typo in utm_campaign is a thirty-second fix rather than a reprint, which is the whole argument for changing a code's destination without reprinting and the broader case for dynamic over static codes. A static code bakes the tags into the ink forever; a dynamic one keeps them under your control.

Before a single flyer goes to print, verify the chain. Open your browser's developer tools, switch to the Network tab, scan or open the code's short link, and check the final landing request. Its URL should still carry your utm_ parameters. If they have gone missing, a redirect hop stripped them, and you have caught it while it is still free to fix.

Reading QR traffic in GA4

Once codes are tagged and the channel group is in place, the data lives where you would expect. Open Reports, then Acquisition, then Traffic acquisition, and set the dimension to Session source / medium or Session campaign. Your QR rows appear as flyer / qr, poster / qr and so on, grouped under the QR channel you created. To confirm a code works the moment it goes live, scan it yourself and watch the Realtime report register the visit. For client reporting, build a free-form Exploration with source, medium and campaign as rows, filtered to medium qr, and you have a per-placement breakdown you can export or schedule. For what to actually report from there, our QR code analytics guide covers which scan metrics earn their place in a client deck.

Why your scan count and GA4 sessions will not match

Expect two different numbers, and do not panic when you see them. Your QR platform counts a scan at the moment of the redirect, before the landing page has loaded. GA4 counts a session only once the page loads, its tag fires, and any required consent is granted. Those are different points in the funnel, so they measure different things. Scan totals run higher because bots and security scanners follow links, browsers prefetch pages, and the same person scans twice to be sure. Session totals run lower because some visitors bounce before the script runs, some block analytics outright, and some decline consent. ScanKit logs the scan server-side at the redirect, which is why its count holds up even when GA4's is suppressed by an ad blocker. Treat the scan number as exposure and the session number as consented, confirmed landings, then reconcile the trend lines rather than chasing an exact match.

A repeatable setup for client campaigns

Pulling it together, the workflow that keeps QR attribution clean across a portfolio of clients is short and worth standardising. Keep each client in its own workspace so codes, destinations and naming never bleed across accounts. Agree a UTM taxonomy once, with utm_medium=qr fixed, utm_source describing the placement, and utm_campaign naming the initiative. Add the QR custom channel group to each client's GA4 property. QA every code's redirect in the Network tab before it goes to print. Then, each month, put the scan count and the GA4 session count side by side and explain the gap rather than hiding it. Do that, and "how many people scanned the flyer?" becomes a question you can answer with a straight face.

Frequently asked questions

Why does my QR code traffic show as direct in Google Analytics 4?

Because the scan reached GA4 with no referrer and no campaign tags, so there was nothing to credit the visit to. Phone cameras and in-app browsers usually send no referrer, and a bare destination URL carries no source information, so GA4 records the session as (direct) and (none) and files it under the Direct channel. Add utm_source, utm_medium and utm_campaign to the destination URL and the source is recorded correctly.

What UTM parameters should a QR code use?

At a minimum, the three Google recommends for every campaign URL: utm_source, utm_medium and utm_campaign. For a QR code, a practical pattern is utm_source for the placement (such as flyer or poster), utm_medium=qr for the channel, and utm_campaign for the client initiative. Add utm_content when you want to compare two versions of the same placement, and utm_id to tie the traffic to a campaign record.

Should utm_medium be qr, offline, or print?

It makes no difference to GA4's defaults, because none of the three maps to a built-in channel; all of them land in Unassigned until you create a custom channel group. Choose for clarity and consistency. Most teams standardise on utm_medium=qr and describe the surface in utm_source. Whichever you pick, use it everywhere and keep it lowercase, because GA4 treats qr and QR as different mediums.

Why is my QR traffic showing as "Unassigned" in GA4?

Because you tagged it with a medium that GA4's default channel rules do not recognise, such as qr. The defaults only match a fixed set of mediums (organic, cpc, email, referral and so on), so anything else falls into Unassigned. Create a custom channel group in Admin, under Data display, with a rule that maps medium qr to a channel named QR, and the traffic reclassifies, including historically.

Does GA4 track QR code scans automatically?

No. GA4 has no concept of a QR scan; it only ever sees a normal web visit to your landing page. It cannot tell a scan apart from a click unless you tell it, which is what the campaign tags on the destination URL do. The scan count itself comes from your QR platform, not from GA4.

Can I change the UTM tags on a QR code after it is printed?

Only if the code is dynamic. A static QR encodes the destination, tags and all, directly into the printed pattern, so changing them means reprinting. A dynamic code encodes a short redirect link instead, and the tagged destination is an editable setting, so you can correct or update the UTM parameters at any time without touching the artwork.

Why do my scan count and GA4 sessions not match?

Because they count different events at different moments. A scan is logged at the redirect, before the page loads; a GA4 session needs the page to load, the tag to fire and consent to be granted. Scans are inflated by bots, prefetching and repeat scans, while sessions are reduced by bounces, ad blockers and declined consent. A gap is normal and expected, so compare the trends rather than the absolute totals.

Do UTM parameters survive a URL redirect?

Only if each redirect passes the query string along. A 301 or 302 sends the browser to the exact address in its Location header, so the ?utm_... string survives only when that target includes it. A well-built dynamic QR redirect forwards the tags intact, while stacking a third-party shortener on top risks dropping them. Verify by scanning the code and checking that the final landing URL in your browser's Network tab still has the parameters.

Should I use a URL shortener with a UTM-tagged QR code?

Avoid stacking a separate shortener on top of a dynamic code. Each redirect hop is another place the tags can be stripped or overwritten, and many shorteners rewrite query strings. A dynamic QR already gives you a short link and a redirect, so let it carry the tagged destination directly rather than adding another layer in between.

The short version

QR scans show up as "direct" because they reach GA4 with no referrer and no campaign tags, and tagging them utm_medium=qr then lands them in "Unassigned" because GA4's default channels do not recognise a QR medium. Fix both: add utm_source, utm_medium=qr and utm_campaign to the destination URL, create a custom channel group that maps qr to its own channel, and check in the Network tab that the tags survive the redirect before anything goes to print. Use a dynamic code so the tagged destination stays editable, and reconcile your scan count against GA4 sessions as two honest measures of the same campaign. Set it up once per client, then go and print the flyer knowing the next "how many scans?" question has a real answer.

Share

Keep reading