Finance asks which campaign paid for Tuesday's order, and the two reports give two different answers. We see this on almost every Shopify stack audit. GA4 Session campaign says spring_sale, while Shopify Analytics says BFCM Email 14 with a string of digits that Messaging stamped on by default.
Both reports are honest about the click they saw.
The labels simply came from different places. The fix is what we call the naming sheet: one approved list that owns utm_source, utm_medium, and utm_campaign, copied exactly by Shopify Messaging and Campaigns, kept off internal links, and checked on the checkout URL after a test click.
Shopify's own UTM guide covers how to build a tagged URL. The gap on live stores is who gets to name things. Messaging, Campaigns, Klaviyo, paid-social macros, and a teammate's bio link will each invent a campaign string unless one convention is locked first.
Every tool already names your campaigns
Shopify Messaging includes a UTM link by default. Growth, Campaigns will mint shareable links, short links, and QR codes. GA4 will read whatever landed in page_location. So the real choice is whether those tools keep their factory labels, or whether the naming sheet is the only writer allowed to name a campaign.
Leave the defaults alone and you get three honest reports that refuse to add up. Messaging writes shopify_email / email / Email name + activity ID. A Meta ad writes facebook / paid_social / Spring Sale 2026. A shortener that strips query strings delivers a session with no campaign at all, and finance ends up asking why GA4 Session campaign and Shopify's Performance by UTM campaign crown two different winners for the same week.
Brands with more than one email tool feel this first. Lanoguard sits on Shopify 2.0 with Klaviyo as a documented partner, so a spring send can leave Messaging with one campaign string and Klaviyo with another unless both teams copy from the same row. Catalogue-heavy storefronts such as RTR Vehicles add dealer referrals and paid social on top, which gives a title-case typo more places to split a row.
What goes on the naming sheet
Google recommends setting utm_source, utm_medium, and utm_campaign together on every manually tagged campaign link. Those three fields populate Session source, Session medium, and Session campaign in GA4 Traffic acquisition, and they are the strings Shopify reads when traffic arrives with utm_campaign in the URL. Write one row per live channel and approve the value before anyone pastes a URL.
| Parameter | What it names | GA4 session dimension | Shopify surface | Locked example |
|---|---|---|---|---|
utm_source | Platform or publication | Session source / Session manual source | Performance by UTM campaign, Growth attribution | instagram, klaviyo, shopify_email |
utm_medium | Channel or traffic type | Session medium | Marketing channel and traffic type splits | email, paid_social, qr_code |
utm_campaign | The promotion, same across channels | Session campaign / Session manual campaign name | Performance by UTM campaign, Sales attributed to marketing | spring_sale_2026 |
utm_content (optional) | Creative or placement inside that campaign | Session manual ad content | Campaigns auto-match may ignore extra params | hero_video_v1 |
utm_term (optional) | Paid keyword you supplied | Session manual term | Supported on Campaigns auto-match | rust_protection |
Three formatting rules belong on the same page. Use lowercase letters, digits, and underscores only, because GA4 treats Pinterest and pinterest as two sources and Shopify groups on the exact string, so BFCM and bfcm split Performance by UTM campaign the same way. Approve one value per platform, since facebook, fb, and facebook.com are three sources and spring_sale, Spring Sale, and spring-sale are three campaigns. Keep the campaign name identical across email, paid social, SMS, and QR, changing only source and medium so the promotion stays one row.
Add utm_content only when the first three fields stay fixed and you are comparing two creatives or two placements. Google's URL builder docs use it for two links in the same email, and Shopify Campaigns help uses red_banner_v1 against blue_banner_v2. Keep it as an optional column.
Customer names, emails, and phone numbers stay out of every value. GA4 collects the landing URL, so a personal detail in a tag travels with the hit.
A missing field writes (not set) on the matching GA4 dimension. A link with source and no medium still creates a session, and Session medium then sits in (not set), which makes the weekly compare against Shopify look like a hole. Write all three required fields every time.
If an ad platform offers utm_campaign={{campaign.name}}, resolve that macro against the naming sheet before the ad goes live. Title-case names from Ads Manager will split the lowercase Shopify row you already created.
Make Messaging and Campaigns copy it
The naming sheet only works once Messaging and Campaigns stop writing their own strings.
Messaging
Open Apps, Messaging, Settings and read the UTM tracking block. Shopify Help documents the factory values as utm_source=shopify_email, utm_medium=email, and utm_campaign set to the email name plus the activity ID. That campaign default causes most of the splits we find, because nobody in finance named a promotion Welcome series 883421 and Messaging wrote it anyway.
Edit the shop-level values so new sends copy the sheet, then open the next campaign and use Edit UTM parameters if that send needs a different campaign name. Shopify says a settings change applies to new campaigns only, and drafts and already-created emails keep the old tags. Plan the cutover for the next send and leave last week's report as it is.
Keep utm_source=shopify_email when Messaging is the only email tool. If Klaviyo or another ESP already uses klaviyo as source, put that value on the sheet and keep Messaging traffic under shopify_email so the two tools stay on separate rows. Medium stays email in both cases.
Shopify also warns that Messaging conversion counts come from store data and will differ from a third-party analytics tool that only sees link clicks. Use Messaging's own email report for opens and clicks, and let the shared UTM values keep GA4 Session campaign and Shopify Performance by UTM campaign naming the send the same way.
Campaigns
Go to Growth, Campaigns and create the campaign using the sheet's utm_campaign value as the name you intend to report. Under Shareable links, add the destination and the remaining UTM parameters before you copy the full URL, the short link, or the QR code.
Shopify attributes every visit from a shareable link to that campaign, and auto-match rules can pull in traffic that already carries the same utm_campaign and utm_source. Help Centre's example matches utm_campaign=elle and utm_source=facebook, then ignores extra landing parameters such as utm_content. If you rely on auto-match, write the rule from the sheet and expect content-level splits to show up in GA4 only.
Leave a short link alone once people have used it. Changing the handle sends later visits to an error page, and deleting a shareable link removes its traffic from the campaign report. Add a new link when you need a new source or a new QR.
Headless and Hydrogen stores cannot create or edit shareable links. Those builds need the tagged full URL in the ad manager, plus an auto-match rule if you still want Campaigns to collect the sessions.
After the first send, open Analytics, Reports and pull Performance by UTM campaign for the same dates you used in GA4. Shopify's October 2026 UTM article still names that report, and Analytics also ships Performance by marketing campaign, which groups by campaign first and then by channel. Check the attribution model before you argue with finance. Last non-direct click is the Shopify default, and it will differ from a GA4 exploration that uses last click or data-driven credit.
For a single order, open conversion details and choose View full sessions. It is the fastest way to see which UTM values actually landed.
Leave internal links bare
Shopify's UTM article says to tag campaign links you share outside the website. Hold to that line.
Theme navigation, collection filters, product cards, cart buttons, and homepage tiles should stay untagged. A new utm_source on an internal hop starts a fresh campaign session in GA4 and can overwrite the paid or email label before the shopper reaches checkout, so the order ends up looking direct, or like a collection click you invented.
Affiliate and collab links are off-site, so they get tags. Preserve the partner's existing query parameters and append UTMs with & when the destination already has a query string. Fragments that start with # go after the query.
Click it yourself before you print it
A tidy naming sheet still fails if the shortener, the QR encoder, or a redirect drops the query string. Shopify's UTM article is blunt here: test the complete journey before you print a code or paste a short link.
We check four stops on that walk. First, the landing URL in the browser still shows all three required parameters after every redirect. Second, GA4 Realtime or DebugView shows those values on the landing page_view page_location. Third, the checkout URL has kept the campaign and not picked up a new internal tag. Fourth, the test order's conversion details, under View full sessions, still list the sheet's source, medium, and campaign.
Google notes that Realtime and DebugView do limited attribution, and Traffic acquisition can take 24 to 48 hours to show the finished Session source / medium and Session campaign. Shopify marketing metrics can take up to 24 hours. Book the compare for the next working day, on the same date range, with the attribution model written down.
Which setup fits your store
One email tool, mostly Messaging
Keep utm_source=shopify_email and utm_medium=email, then replace the campaign default with the sheet's promotion name, such as spring_sale_2026, before the next send. Leaving Messaging to join the email title and activity ID is how last week's report grew a second row.
Messaging plus Klaviyo or another ESP
Give each tool its own source on the sheet, shopify_email and klaviyo, with the same campaign name and medium staying email. Finance can then split volume by tool without splitting the promotion.
Paid social and email for one offer
Fix utm_campaign first, then change only source and medium, such as instagram / paid_social and shopify_email / email, and put creative tests in utm_content. With Campaigns auto-match on, Shopify may roll those creatives into one campaign row while GA4 still splits on content.
QR codes, inserts, and short links
Build the full tagged URL first, encode that URL into the QR or the shortener, and test the scan on a phone that has never opened the store. Add a new shareable link for a second physical placement, and leave the handle of a code already printed alone.
Headless or Hydrogen
Skip Campaigns shareable links on these builds, put the tagged full URL in the ad platform, and add an auto-match rule if you still want Growth, Campaigns to collect the sessions.
Back to Tuesday's order
When finance asks again, GA4 Session campaign and Shopify Performance by UTM campaign should both say spring_sale. Put the naming sheet in place before the next send.
If several tools already write campaign strings, or a redirect is eating the query before checkout, we can walk the live links as part of a stack assessment or through integrations and system optimisation. Bring the current Messaging UTM settings, one Campaigns shareable link, and a GA4 Traffic acquisition export for the same week.



