Tracking

How to Track QR Code Scans in Google Analytics 4

The clean setup uses two measurement layers: the QR platform records that a specific printed code was scanned, and GA4 records what the visitor did after arriving on your site. They answer different questions, they will not agree on the numbers, and trying to make one behave like the other is where most QR tracking setups go wrong.

Written by
VastQR Product & Editorial Team
Last updated
On this page
  1. The two-layer setup
  2. What GA4 actually measures
  3. Decide naming first
  4. UTM builder
  5. Step-by-step setup
  6. A UTM template
  7. Separating placements
  8. Direct and Unassigned
  9. Why the numbers differ
  10. Editing a live destination
  11. GA4 and static codes
  12. FAQ

The two-layer architecture

  1. Printed QR
  2. VastQR redirect
  3. Tagged destination URL
  4. Your website
  5. GA4
VastQR measures the handoff at step two. GA4 measures the visit at step five.

An example destination:

https://example.com/menu?utm_source=offline&utm_medium=qr&utm_campaign=spring_menu&utm_content=richmond_table_14

Nobody sees that URL. The printed artwork encodes the short redirect, so the parameters can be long and descriptive without making the QR pattern denser or harder to scan.

The questionAnswered by
Did Table Tent 14 get used?QR redirect analytics
Which placement produced the most scans?QR redirect analytics
Did those visitors read the menu?GA4
Did the campaign produce orders?GA4
How many requests never became a page view?The gap between the two

What GA4 actually measures, and where it starts

Worth being blunt about, because almost every “GA4 is under-reporting my QR codes” conversation starts here: GA4 never sees the scan. It has no visibility of a camera recognising a pattern. It starts existing much later in the chain, and everything before that point is invisible to it by construction.

  1. Camera recognises the code
  2. User taps the link
  3. Redirect receives the request
  4. Browser follows it
  5. Page begins loading
  6. GA4 tag runs
VastQR can speak for box three. GA4 can speak for box six. Nothing speaks for boxes one and two.

A person can be recognised by the camera and never tap. They can tap and close the tab before the page loads. Consent tooling, a blocker, a dead zone or a slow page can all end the journey between box three and box six. That is why:

Redirect requests are not sessions, and neither number is wrong
A gap between the two is the normal shape of this measurement, not a defect to hunt. Treat a persistent ratio as the signal worth watching; treat an exact match as a reason to check whether one of the two systems is actually measuring what you think.

Decide the naming convention before you print

The most common GA4 mistake is not a missing parameter. It is three people using qr, QR and print for the same thing, and someone spending a day cleaning it up in a spreadsheet six weeks later.

Being precise about what is and is not recoverable here matters, because the two halves behave differently. A dynamic QR code’s destination stays editable after printing, and so do the UTM parameters attached to it — you can fix a convention at any point, and every scan from then on arrives under the corrected names. What no product can do is go back and rename the sessions GA4 already filed. Those months stay split across whatever names were in use at the time.

So the cost of deciding late is not a reprint. It is a gap in the record. Pick once, write it down, and apply it to every destination in the batch:

ParameterWhat it should carryExample
utm_sourceThe origin familyoffline
utm_mediumThe mechanismqr
utm_campaignThe business campaignfall_menu_2026
utm_contentThe physical placementrichmond_table_14
utm_termUsually unnecessary for QRleave empty unless you have defined a use
Use the taxonomy you already have
If your organisation already has campaign naming rules for paid and email, extend them. Inventing a QR-only scheme means your offline channel cannot be compared with anything else in the same report.

One consequence is worth knowing before you commit: a custom value such as utm_medium=qr may not match any of GA4’s default channel rules, so the traffic can appear under Unassigned even when Session source / medium shows your values correctly. That is a labelling problem with a proper fix, and it is covered below — it is not a reason to pick a dishonest medium.

QR UTM Campaign Builder

Build the tagged destination for one printed placement. Source and medium start on the convention above; everything stays editable, and any query parameters your URL already carries are preserved.

The page the code should open. Existing query parameters are kept.

offline and qr are filled in as a starting convention, not a rule — see the naming section above. What matters most is not changing convention halfway through a campaign.

Enter a destination and the three required parameters to build the tagged URL.

This builds the tagged destination. It does not mean GA4 recorded a scan: a redirect request and a GA4 session measure different boundaries, and consent, ad blockers and visitors who leave before the page loads all sit between them.

Need codes whose destination you can change after printing? Create trackable dynamic QR codes with VastQR.

Setting it up, step by step

  1. Confirm GA4 is already working on the destination site

    Open the page and check the property is receiving ordinary web activity. Do this first — otherwise you will spend an afternoon blaming QR attribution for a missing tag.

  2. Build the tagged destinations

    One row per placement, each with the same campaign and a distinct utm_content. A spreadsheet is the right tool here: it is easy to see at a glance whether the taxonomy was applied consistently.

  3. Create the dynamic codes from that list

    Paste or upload the CSV so each tagged URL becomes the destination of one managed code. The parameters live in the destination, not in the printed pattern.

  4. Scan one before printing anything

    On a real phone. Confirm the redirect resolves, the final URL loads, the parameters survived the redirect, and GA4 registered the session.

    Parameters surviving the redirect is the step people skip and the step that most often breaks.

  5. Verify traffic-source reporting in GA4

    Check Traffic acquisition, or build an exploration on the session-scoped source, medium, campaign and content dimensions. Be explicit about whether you are looking at user-, session- or event-scoped dimensions; they will not agree, and that is by design.

    Because a QR campaign is manually tagged, the fastest place to confirm the values is GA4’s Manual report, which exists specifically for manually tagged traffic. The dimensions to read there are named:

    • Session manual source
    • Session manual medium
    • Session manual campaign name
    • Session manual ad content — where your utm_content placement lands

    Read the raw dimensions before you judge the channel label. Session source / medium can be perfectly correct while the default channel group still reads Unassigned — see the section below.

  6. Print a physical proof and scan that

    At final size, from the distance people will actually stand. A browser test proves the tagging; only the printed proof proves the deployment.

Have your tagged destinations ready?

Paste the list, see which rows validate, and preview the whole batch before you pay for anything.

A reusable template for a QR campaign

Sixty codes across three stores, one campaign called summer_combo_2026. Every destination shares the source, medium and campaign; only the content changes:

Placementutm_contentEverything else
Richmond, table 1richmond_table_01source=offline · medium=qr · campaign=summer_combo_2026
Burnaby, table 1burnaby_table_01identical
Vancouver, windowvancouver_window_01identical

Now GA4 can report the campaign as one thing and still break it down by placement, while VastQR reports which individual code was scanned. Two systems, one taxonomy, no conflict.

Separating placements without wrecking your reports

There are two strategies, and only one of them scales.

Unique utm_content — use this by default

Right whenever the placements belong to the same campaign. The campaign stays legible and the detail is still there when you want it.

Unique campaign names — only for genuinely separate campaigns

Right when the placements have different objectives and different owners. Wrong as a way of separating three hundred table cards, which turns the campaign report into a directory.

The failure mode
Three hundred campaign names is not granular reporting. It is a report nobody opens twice.

When GA4 says Direct, or says Unassigned

These are the two reports that send people looking for a broken QR code, and they mean opposite things. One says the campaign data never arrived. The other says it arrived and Google did not recognise the category you put it in. Fixing the second the way you would fix the first will corrupt your reporting.

Which one are you looking at?

  • (direct) / (none)The session carried no usable campaign or referral information. The tags are missing or were lost on the way.
  • offline / qr, channel UnassignedThe tags arrived intact. GA4's default channel rules simply have no bucket for your medium. Nothing is broken.
  • (not set) source / mediumSession or tag configuration, not the QR code. Check the property is receiving ordinary web activity at all.

Direct means the parameters did not survive

For a QR campaign, that usually has one of four causes: the printed destination never carried parameters; a redirect somewhere in the chain stripped them; the final landing URL lost them before GA4 read the page; or the session started without any attribution information to work from.

This is why the pre-print test has to inspect the final landing URL rather than simply confirm the code opens something:

  1. Printed QR
  2. Redirect
  3. Landing URL still carries the parameters
  4. GA4 records the visit
If the third box fails, GA4 files the session as Direct — and no report built later can recover what was never collected.

Unassigned means the tags worked and the label did not

GA4’s default channel group is a fixed set of classification rules. A medium like qr can be exactly right for your own taxonomy and still match none of them. What you see then is:

Session source / medium      = offline / qr
Session default channel group = Unassigned

The attribution is correct. Only the label is missing. Diagnose in this order and you will not confuse the two cases:

  1. Check Session source / medium

    If it reads offline / qr, your parameters arrived. Stop suspecting the QR code.

  2. Check the campaign dimensions

    Session campaign and the manual content dimension should carry the names you defined before printing.

  3. Confirm the values survived the redirect

    Open the short link yourself and read the address bar on the landing page.

  4. Only now look at the channel group

    If steps one to three are correct, what you have is a classification problem, and it is fixed in reporting rather than in tagging.

Do not rename the medium to force it into a bucket

Labelling QR traffic email or referral because those map neatly into a default channel is a lie you will have to keep telling. Keep the taxonomy that describes what actually happened, and build a custom channel group — something like Offline QR — with a rule matching the source and medium you really use.

A custom channel group re-categorises data that was already collected. It does not rewrite the underlying source and medium values, and it cannot recover parameters that were never captured in the first place. That asymmetry is the whole argument for deciding your naming before the print run.

The same four symptoms, and where to look first.
What GA4 showsWhat it actually meansFirst thing to check
(direct) / (none)Campaign information missing or lost in transitDid the final landing URL still carry the parameters?
offline / qr + UnassignedTags arrived; default channel rules did not match themSession source / medium — then leave the tags alone
(not set)Session or tag configuration issueWhether the property receives ordinary web traffic
Right source, wrong groupingA reporting taxonomy problemCustom or primary channel-group rules

Why redirect counts and GA4 sessions never match

Between a scan and a GA4 session there are seven things that have to happen: the camera recognises the code, the person taps the link, the redirect receives the request, the browser follows it, the destination begins loading, the GA4 tag executes, and consent and browser settings permit measurement. Any of them can fail independently.

Divergence is therefore expected. What matters is that each system is internally truthful about what it measured.

What happenedQR redirect countGA4 session
Person scans, page loads normallyYesLikely yes
Person opens, then closes before the page loadsYesOften no
A link preview or crawler requests the short linkYes, labelled where recognisedUsually no
Page loads but consent is denied or the tag is blockedYesOften no
Visitor refreshes the landing pageNo new requestMore GA4 activity
Same person scans three timesThree eventsDepends on session timing

Automation is a large part of that gap. VastQR counts recognised automated requests in its default All events view and reports the classified share, and offers an Exclude classified automated view when you want the period recalculated without them. Neither view is a verified-person count — the classifier only sees what announces itself — but having the figure in front of you is what makes the difference between the two systems explainable.

Do not demand equality between the two. Demand that you can explain the gap.

What actually counts as a scan →

What happens to attribution when you edit a live destination

A dynamic code lets you change the destination after printing, which creates a reporting decision most guides skip.

  • Same campaign, page moved: keep the taxonomy identical. Changing it splits one campaign into two for no analytical reason.
  • New campaign entirely: update the parameters along with the destination, because the old campaign name no longer describes what the visitor is seeing.

Either way, write down the date. The parameter change applies to traffic from that moment on; the sessions GA4 already recorded keep the names they were filed under. A before-and-after comparison that silently spans both is worse than no comparison.

GA4 does not make a static code dynamic

You can put a tagged URL inside a static QR code and measure the resulting website traffic in GA4. That is genuinely useful, and it is why the claim “static QR codes cannot be tracked” is wrong.

What GA4 does not give the static code:

  • an editable destination;
  • a managed redirect;
  • a pause state;
  • a library of codes to manage;
  • a scan count for the code itself, as opposed to a session count for the page.

That is the exact line between website attribution and dynamic QR operations.

What redirect-level QR tracking shows →

Frequently asked questions

Do I need GA4 to track QR scans?
No. A dynamic QR platform records redirect-level scan events on its own. GA4 adds what happens after the visitor reaches your website — engagement, key events, conversions.
Can a static QR code use GA4?
Yes. Put campaign parameters in the destination URL and GA4 will read them when the page loads. You get website attribution without the code being dynamically managed.
Why are my QR scans higher than my GA4 sessions?
Because the two measure different boundaries. A redirect request is recorded even when the visitor closes the tab before the page loads, when a crawler touches the link, or when consent or an ad blocker stops the GA4 tag.
Why does my QR traffic show up as Direct in GA4?
Because GA4 received no usable campaign information for that session. Check that the printed destination actually carries the parameters and that they survive every redirect all the way to the final landing page — read the address bar on the page that loads, not just the link you configured.
Why does utm_medium=qr sometimes show as Unassigned?
Because GA4’s default channel group only recognises traffic matching its own rules, and qr is not one of them. Your Session source / medium can still correctly read offline / qr. If you need a dedicated reporting bucket, build a custom channel group rather than renaming the medium to something untrue.
Should every QR code get a unique UTM?
Not a unique campaign. Use one campaign for the campaign, and a unique utm_content per placement. Three hundred campaign names make GA4 unreadable.
Does VastQR integrate with GA4 automatically?
No. There is no native integration. The connection is the campaign-tagged destination URL you configure — VastQR measures the redirect, GA4 measures the site.
Should utm_source be qr or offline?
Either can be internally consistent. Treating offline as the source and qr as the medium usually extends better, because the same source family can then hold other offline mechanisms. What matters far more is not changing convention halfway through a campaign.

Sources