How do I set up UTMs on QR codes so GA4 shows which poster sent each visit?

Add UTM parameters to each QR code's destination URL, with utm_medium set to ooh or print and utm_content set to the individual placement, then read them in GA4 under Reports, Acquisition, Traffic acquisition. Without UTMs, visits from a printed QR code usually show up in GA4 as Direct, with nothing to say which poster they came from.

By Daniel Zaremba, founder of BetterQR · Updated

Why do QR code visits show as Direct in GA4?

When someone clicks a link on a website, the browser usually tells the next site where the visitor came from (the referrer). A phone camera opening a URL from a QR code passes no referrer, so GA4 has nothing to go on and files the session under Direct, alongside people who typed your address or used a bookmark. UTM parameters fix that by carrying the source in the URL itself.

What are the five UTM parameters?

ParameterAnswersPrint and OOH conventionGA4 dimension
utm_sourceWho or where sent the visitMedia owner, venue or print run, e.g. bus-shelters, leaflet-dropSession source
utm_mediumWhat kind of channelAlways ooh for outdoor, print for flyers, packaging and menusSession medium
utm_campaignWhich campaignCampaign and period, e.g. spring-sale-26Session campaign
utm_contentWhich placement or creativeThe individual site, e.g. shelter-station-rd-cSession manual ad content
utm_termPaid search keywordOptional; some teams use it for the creative version (design-a)Session manual term

utm_source, utm_medium and utm_campaign are the ones to always set. For OOH, utm_content matters just as much, because it's the only tag that separates one poster from another.

What UTM naming scheme should I use for print and OOH?

GA4 treats UTM values as case-sensitive text, so OOH and ooh become two separate rows. Use these rules:

  • Lowercase everything.
  • Hyphens inside a value, no spaces (spaces turn into %20).
  • Keep a small fixed list of mediums: ooh, print, and nothing else for offline.
  • Put the most specific detail in utm_content, never in the campaign name, so campaign totals still add up.
  • Keep the scheme in a shared spreadsheet and copy from it rather than typing values by hand.

Example values for one campaign across formats:

Placementsourcemediumcampaigncontent
48-sheet billboard, A40billboardoohspring-sale-2648sheet-a40-west
Bus shelter, Station Roadbus-shelteroohspring-sale-26shelter-station-rd-c
A4 poster in a partner cafépartner-venueoohspring-sale-26poster-corner-cafe
Door-drop flyer, north postcodesleaflet-dropprintspring-sale-26flyer-n-postcodes
Insert card in parcelspackagingprintspring-sale-26parcel-insert-v1

The finished URL for the bus shelter looks like this: https://example.com/spring?utm_source=bus-shelter&utm_medium=ooh&utm_campaign=spring-sale-26&utm_content=shelter-station-rd-c. The QR code UTM builder assembles the URL and its QR code in one step.

Should the UTMs go inside the QR code?

Not if you can avoid it. A static QR code has to contain the whole URL, and a 130-character UTM link makes a much denser pattern than a 30-character one, which is harder to scan from a distance (see billboard QR code size). It also fixes the tags forever: a typo in utm_content means reprinting.

With a dynamic code, the printed pattern holds a short redirect link and the UTMs live on the destination. In BetterQR you set source, medium, campaign, term and content on the code (Pro and up); they're added to the destination when someone scans, so the printed code stays short and you can correct a tag after printing.

Where do I see QR code traffic in GA4?

GA4's interface changes from time to time, so menu names may differ slightly, but the standard path is:

  1. Open Reports, then Acquisition, then Traffic acquisition.
  2. The table defaults to the session channel group. Use the dimension drop-down above the first column to switch to Session source / medium. Your codes show as rows such as bus-shelter / ooh.
  3. Switch to Session campaign to see campaign totals.
  4. To split by placement, add a secondary dimension (the plus icon next to the dimension name) and pick Session manual ad content, which holds your utm_content values.
  5. Use the search box above the table to filter to ooh or your campaign name.

For a reusable view, build a free-form exploration under Explore: rows for Session campaign and Session manual ad content, values for Sessions, Engaged sessions and Key events, and a filter where Session medium exactly matches ooh. This becomes your per-placement report.

Why does my OOH traffic show as Unassigned?

GA4's default channel group has rules for mediums like email, cpc and social, but none for offline media, so sessions tagged ooh or print usually land in Unassigned in the channel view. Your data is fine; look at source / medium or campaign instead. If you want an "Offline" channel, an admin can create a custom channel group (under Admin, Data display, Channel groups) with a rule matching those mediums.

How do I track conversions from QR codes in GA4?

Mark the action you care about (a purchase, a form submission, a booking) as a key event in GA4. Once it is, the Traffic acquisition report and your exploration show key events per source, medium, campaign and placement. Make the landing page match the offer on the poster: a QR code that lands on a generic homepage loses people before they convert.

Why are there more QR scans than GA4 sessions?

A dynamic code counts the scan during the redirect, before your page loads. GA4 only counts a session if its tag runs on your page and is allowed to. So scans are almost always higher than sessions. Common reasons:

ReasonEffect
Visitor declines analytics cookiesGA4 records nothing, or only modelled data if consent mode is set up
Ad or tracker blockers on the phoneGA4 tag never loads
Visitor closes the page before it loadsScan counted, no session
Same person scans twice in a few minutesTwo scans, one session
Destination isn't your website (app store, PDF, map)No GA4 tag at the destination at all
Redirect on your site strips the query stringSession arrives untagged, shows as Direct

The gap is normal. What matters is that it's roughly stable across placements. If one placement has far fewer sessions per scan than the rest, check its destination URL first: a broken tag or a redirect that drops the UTMs is the usual cause.

The reverse can happen too. If someone copies the tagged URL from their browser and shares it, those visits count as sessions with no scan. That's another reason to keep the scan count from the code as your primary response number and use GA4 for what happens after the visit.

Can I see UTM data without GA4?

Yes. On Business and up, BetterQR breaks each code's scans down by the UTM source, medium and campaign they carried, and you can export scans to CSV for a spreadsheet or Looker Studio. The OOH attribution guide explains how scan data and GA4 fit together in one dashboard.

Quick setup checklist

  • One code per placement, each with its own utm_content
  • utm_medium is ooh or print, nothing else
  • All values lowercase, hyphenated, copied from one spreadsheet
  • UTMs on the destination of a dynamic code, not printed in a static one
  • Open each tagged URL once and confirm it appears in GA4 Realtime
  • Your main conversion marked as a key event
  • A saved exploration: campaign × manual ad content × sessions and key events

Give every placement its own code

Set up a dynamic QR code per poster, flyer or billboard in a few minutes and see which one brings customers in.

ICO registration ZA921111 (opens in new tab)