The two-layer architecture
- Printed QR
- VastQR redirect
- Tagged destination URL
- Your website
- GA4
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 question | Answered 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 |
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:
| Parameter | What it should carry | Example |
|---|---|---|
utm_source | The origin family | offline |
utm_medium | The mechanism | qr |
utm_campaign | The business campaign | fall_menu_2026 |
utm_content | The physical placement | richmond_table_14 |
utm_term | Usually unnecessary for QR | leave empty unless you have defined a use |
Setting it up, step by step
- 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.
- 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. - 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.
- 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.
- 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.
- 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:
| Placement | utm_content | Everything else |
|---|---|---|
| Richmond, table 1 | richmond_table_01 | source=offline · medium=qr · campaign=summer_combo_2026 |
| Burnaby, table 1 | burnaby_table_01 | identical |
| Vancouver, window | vancouver_window_01 | identical |
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.
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 happened | QR redirect count | GA4 session |
|---|---|---|
| Person scans, page loads normally | Yes | Likely yes |
| Person opens, then closes before the page loads | Yes | Often no |
| A link preview or crawler requests the short link | Yes, labelled where recognised | Usually no |
| Page loads but consent is denied or the tag is blocked | Yes | Often no |
| Visitor refreshes the landing page | No new request | More GA4 activity |
| Same person scans three times | Three events | Depends on session timing |
Do not demand equality between the two. Demand that you can explain the gap — which is exactly what a visible automated-traffic figure on the QR side is for.
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.