Traffic lands in Unassigned when GA4 receives a source and medium pair its channel rules don’t recognize, and it piles into (other) when campaign values are so varied that reports hit their row limits. The fix is a short, fixed vocabulary for source and medium, one pattern for campaign names, and a builder sheet everyone has to use.
Why messy UTMs break reporting
UTM parameters are free text, and everyone who hand-types a link invents a slightly different value. Each variation does specific damage:
- Unassigned traffic. A pair like
newsletter / blastorpartner-x / influencermatches no default channel rule, so it lands in Unassigned. - Misclassified traffic.
facebook / social-paidisn’t recognized as paid, but the source is on GA4’s social list, so paid spend shows up as Organic Social. - Split rows. GA4 treats
Facebook,facebookandfbas three different sources, spreading paid social across rows nobody adds up. - (other) rows. When campaign or content values include timestamps, per-send IDs or one-off names, unique values multiply and reports roll the long tail into (other).
When I audit GA4 properties, the cause is rarely one big mistake; it’s years of small inconsistencies. Messy UTMs also make GA4 harder to reconcile with ad platforms (see why GA4 and ad platforms don’t match).
How GA4 reads UTM parameters
GA4 maps each parameter to a session-scoped dimension:
| Parameter | GA4 dimension | Use it for |
|---|---|---|
| utm_source | Session source | Where the click came from: facebook, bing, klaviyo |
| utm_medium | Session medium | The type of traffic: cpc, paid_social, email |
| utm_campaign | Session campaign | The specific campaign or initiative |
| utm_content | Session manual ad content | Which ad, creative or link |
| utm_term | Session manual term | Keyword or audience |
| utm_id | Session campaign ID | Campaign ID, a key for cost data imports |
The channel is not a parameter. GA4 derives it from source and medium, and sometimes campaign name, using its default channel group rules (the full list is in Google’s GA4 help center). The ones that matter most:
- Paid channels need a medium matching
^(.*cp.*|ppc|retargeting|paid.*)$, socpc,ppcand anything starting withpaidqualify. The source then picks the channel: a recognized social source gives Paid Social, a search engine Paid Search, a video site Paid Video, anything else Paid Other. - Organic Social triggers on a recognized social source or a medium such as
social. - Email, SMS, Affiliates, Audio, Referral and Mobile Push Notifications trigger on specific mediums:
email,sms,affiliate,audio,referral, and anything ending inpush. - Campaign names count too. A name containing “cross-network” goes to Cross-network. “Shop” or “shopping” in a campaign name can pull traffic into Paid Shopping or Organic Shopping, and the pattern also catches words like
shopifyandshop-now.
Rules for source and medium
Source and medium decide the channel, so four rules apply:
- Lowercase only. GA4 reports are case-sensitive.
- Letters, numbers, hyphens and underscores only. No spaces, ampersands or accents.
- Source is the platform or publisher, spelled out in full (
facebook, notfbormeta-ads) so it matches GA4’s source lists. Never the campaign, audience or ad name. - Medium is the traffic type and comes from a closed list. Nobody adds one without the owner’s approval.
The approved vocabulary:
| Traffic | utm_source | utm_medium | GA4 channel |
|---|---|---|---|
| Meta (incl. Instagram), LinkedIn or TikTok ads | facebook, linkedin, tiktok | paid_social | Paid Social |
| Microsoft Ads | bing | cpc | Paid Search |
| Programmatic display | network name | display | Display |
| Organic social posts | linkedin, x, facebook | social | Organic Social |
| Email to your own list | klaviyo or hubspot | ||
| SMS | sending platform | sms | SMS |
| Affiliates | network or partner name | affiliate | Affiliates |
| Partner and PR links | partner domain | referral | Referral |
| Podcast sponsorships | show name | audio | Audio |
QR codes, events and print have no default channel. Give them one consistent medium (qr, event) and a custom channel group rather than forcing them into the wrong channel.
Campaign, content and term conventions
utm_campaign
Build it from fixed parts in a fixed order, joined with underscores:
[yyyy-mm]_[objective]_[audience]_[offer]_[geo]
Example: 2025-11_prospecting_saas-founders_growth-audit_us
- Objective comes from a short list:
prospecting,retargeting,retention,launch,brand. - Audience and offer join words with hyphens, never underscores.
- Geo is optional, goes last and uses country codes.
- Never skip a middle part. Use
allwhen a campaign targets everyone, so every name splits into the same columns by position.
The date prefix sorts campaigns and makes a quarter easy to filter; always-on campaigns use evergreen in its place. Keep “shop” out of every part unless it really is a shopping campaign.
utm_content
Identify the creative or link, not the campaign: [format]_[concept]_[variant], for example video_founder-story_v2 or static_pricing-table_v1. In email, use the link’s position: hero-cta, footer-link.
utm_term and utm_id
Paid search keywords come through auto-tagging, so use utm_term for social targeting: lookalike-purchasers, job-title-cmo. Pass the platform’s campaign ID in utm_id wherever a dynamic value exists; it’s part of the join key for GA4 cost data imports and for merging spend in a dashboard.
Never put personal data in UTMs. No email addresses, names or phone numbers; Google Analytics policies prohibit sending personally identifiable information. Skip per-subscriber IDs too; they only feed the (other) row.
Platform-by-platform examples
Google Ads. Leave auto-tagging on and link the account to GA4; the click ID carries source, medium and campaign.
Microsoft Ads. Turn on UTM auto-tagging in account settings; it applies bing / cpc plus campaign values.
Meta. Add URL parameters at the ad level with dynamic values:
utm_source=facebook&utm_medium=paid_social&utm_campaign={{campaign.name}}&utm_term={{adset.name}}&utm_content={{ad.name}}&utm_id={{campaign.id}}
Dynamic values are only as clean as your Ads Manager names, and renaming a live campaign splits its rows in GA4.
LinkedIn. Add URL parameters in Campaign Manager’s campaign settings with utm_source=linkedin&utm_medium=paid_social and your campaign name.
TikTok. Add parameters at the ad level; its name macros are safe only if campaign, ad group and ad names follow the convention.
Email (Klaviyo, HubSpot). Both can append UTMs automatically; if their defaults put a campaign or flow name in utm_source, email scatters across hundreds of sources. Use the platform as source, email as medium and the send or flow name as campaign; map any default you can’t change.
Organic social and bio links. utm_source=linkedin&utm_medium=social&utm_campaign=evergreen_brand_all_founder-posts.
QR codes and print. utm_source=packaging&utm_medium=qr&utm_campaign=2025-11_retention_customers_insert-card. Point the code at a redirect you control, so you can fix parameters without reprinting.
Governance: builder sheet and ownership
Conventions hold when the easiest way to create a link also enforces the rules.
The builder sheet
One Google Sheet with two tabs:
- Dictionary. Approved channels, their source options and the one medium each uses. Values are defined only here.
- Builder. One row per link, with date, owner and destination so bad values can be traced. Channel, source, objective and audience are dropdowns fed by the dictionary, set to reject other input; medium fills in from the channel.
Useful formulas, assuming the destination URL in column A, channel in B, source in C, medium in D, the five campaign parts in E to I, campaign in J and content in K:
- Campaign:
=LOWER(TEXTJOIN("_",TRUE,E2:I2)) - Final URL:
=A2&IF(ISNUMBER(FIND("?",A2)),"&","?")&"utm_source="&C2&"&utm_medium="&D2&"&utm_campaign="&J2&IF(K2="","","&utm_content="&K2) - Check:
=IF(REGEXMATCH(C2&D2&J2&K2,"^[a-z0-9_-]+$"),"OK","FIX")
The FIND test handles destinations that already have a query string. Parameters must sit before any # anchor, so strip anchors from the destination column. Format the month column as plain text, or Sheets turns 2025-11 into a date.
One named owner
Name one person who owns the dictionary, usually whoever runs analytics or marketing ops. They approve new values before use, review GA4 monthly for values outside the dictionary, and walk every new hire, agency and freelancer through the sheet.
The rules they enforce:
- Every external link comes from the builder, including agency and partner links
- No UTMs on internal links or site banners
- Short links are created from the tagged URL, not tagged afterward
- Redirects preserve query strings (test one per domain)
- Ad platform names follow the convention wherever dynamic values are used
Setting this up is one of the first things I do in a marketing attribution engagement, because every dashboard downstream depends on it.
Fixing historical UTM mess
GA4 doesn’t let you edit collected data, so old UTMs can’t be rewritten. They can be reclassified.
- Export the damage. In the Traffic acquisition report, set 12 months, use Session source / medium with Session campaign as a secondary dimension, and export sessions and key events. Use this standard report, not an exploration, which only reaches back as far as your data retention setting (two months by default).
- Build a mapping table. One row per messy pair, with the clean source, medium and channel it should have had.
- Create a custom channel group under Admin, Data display, Channel groups. Copy the default group and add rules that catch legacy values, such as
social-paidfrom a social source going to Paid Social. Rules run top to bottom, so place each one above the default channel that would otherwise claim the traffic. Custom channel groups apply to historical data too. - Apply the mapping in your reporting for anything a channel rule can’t express, by joining the table in Looker Studio or BigQuery. In an automated pipeline, it belongs in the normalization step (see automating marketing reporting).
- Shut off the sources. Find live links still producing bad values: old ads, email templates, bio links, partner pages, printed QR codes. Replace them, or the cleanup never ends.
An example mapping table:
| Messy source / medium | Clean source / medium | Channel |
|---|---|---|
| Facebook / Social-Paid | facebook / paid_social | Paid Social |
| newsletter / Email | klaviyo / email | |
| partner-x / influencer | partner-x / affiliate | Affiliates |
Some Unassigned traffic has nothing to do with UTMs: server-side or Measurement Protocol events sent without session information land there too. If Unassigned stays high after cleanup, check how your tags pass session data.
Get it built
If more of your traffic sits in Unassigned than in any real channel and you want the convention, builder sheet and cleanup done for you, start with the Growth Audit: $1,500 fixed, credited if we continue. See pricing or get in touch.