Skip to content
← All articles

Analytics & Tracking

Why Your Best Campaign Shows Up as Four Mediocre Ones

GA4 compares campaign values character by character, so one campaign tagged four ways is four rows. The convention that prevents it, how to find the duplicates you already have, and what can still be salvaged in BigQuery.

September 23, 2026 · 9 min read

One long bar above four short ones whose labels differ only in spelling

Open the traffic acquisition report and sort by sessions. Somewhere down the list, past the rows you expected, you find Spring_Appeal. A bit further, spring appeal. Then Spring-Appeal, and near the bottom, spring_appeal.

That is one campaign. It ran once, it worked, and your reporting has quartered it. Each of the four rows looks like a mediocre performer, so the campaign that actually paid for itself gets cut in the next planning meeting, and the one that looked fine because nobody spelled it four different ways survives.

Nothing is broken. The tags were all applied. They just were not applied consistently, and consistency is the only thing that makes campaign reporting worth reading.

Why does one campaign show up as several rows?

Because GA4 stores what you send, exactly as you send it, and compares values character by character.

Facebook and facebook are two different values. So are spring_appeal and spring_appeal with a trailing space, which is easy to produce when someone copies a name out of a spreadsheet cell. Nothing lowercases your tags on the way in and nothing merges near matches once they are in.

This is not a flaw so much as a consequence. Analytics cannot know that FB and facebook are the same thing, because in another account they might genuinely be different. The judgement has to happen before the link is built, which means it has to happen in a rule your team follows rather than in the tool.

The cost is quiet, which is what makes it dangerous. Nothing errors. You just make decisions from a report where every number is a fraction of the truth, and where the fraction is different for each campaign depending on how many people touched the links.

Which UTM parameters actually matter?

Five in common use, and they are not equally important.

  • utm_source is where the click came from: google, facebook, mailchimp, a partner's domain. One value per platform, forever.
  • utm_medium is the type of traffic: cpc, email, social, affiliate. This one does more work than the rest combined, for reasons in the next section.
  • utm_campaign is the initiative: spring_appeal, giving_tuesday_2026. This is the one that fragments most often, because it is the one people invent fresh every time.
  • utm_content distinguishes links that are otherwise identical: which button, which image, which variant.
  • utm_term was built for paid search keywords and is often left empty. That is fine.

There is also utm_id, which GA4 uses as a campaign ID and which matters if you import cost data, since it is what the import joins on. If you are not importing cost, you can ignore it for now.

The practical point: source and medium are a fixed vocabulary you decide once. Campaign is a naming pattern. Content and term are free text within a campaign. Treating all five as free text is where the trouble starts.

How does utm_medium decide your channel report?

Your Channels report is not a separate thing you configure. GA4 derives it, mostly from medium, using a fixed set of rules.

A medium of cpc or ppc puts a session in a paid channel. email lands in Email. social in Organic Social. referral in Referral. The rules also look at source, which is why google plus cpc becomes Paid Search while google plus organic becomes Organic Search.

Send a medium GA4 does not recognise and the session does not land anywhere sensible. newsletter instead of email, facebook in the medium field instead of the source field, Email with a capital E: each of these can push traffic into Unassigned or into the wrong channel, where it is invisible to everyone reading the report at channel level.

This is the single best argument for a fixed vocabulary. Campaign names being messy costs you detail. Mediums being messy costs you the channel report, which is the view most people actually look at.

What does a naming convention look like?

Short enough to remember, or nobody follows it. Three rules carry most of the value.

Lowercase everything. Not because lowercase is better, but because it is a rule with no judgement in it. Any convention that requires someone to remember whether it is Spring or spring will be broken within a month.

Fix the vocabulary for source and medium. Write the permitted values down, keep the list short, and treat adding to it as a decision rather than a typing choice. Most organisations need perhaps six mediums and a dozen sources.

Pick one separator and one campaign pattern. Underscores or hyphens, not both, and never spaces, which become %20 in the URL and then a different value again. A pattern like season_initiative_year gives you spring_appeal_2026 and, more usefully, makes campaigns sortable and greppable later.

Then write it down somewhere a person building a link will actually look, which is not a shared drive.

How do I find the duplicates I already have?

Start in GA4, because it takes two minutes and usually settles the question.

Build a free form exploration with Session campaign as the row dimension and Sessions as the metric. Sort alphabetically rather than by size. Near duplicates sit next to each other alphabetically, so the problem becomes visible immediately: four rows in a row that are obviously the same campaign. Repeat with Session source and Session medium, where the list should be short enough to read in one screen.

For the thorough version, use the BigQuery export, where you can ask how bad it is rather than eyeballing it:

SELECT
  LOWER(TRIM(collected_traffic_source.manual_campaign_name)) AS normalised,
  COUNT(DISTINCT collected_traffic_source.manual_campaign_name) AS spellings,
  STRING_AGG(DISTINCT collected_traffic_source.manual_campaign_name, ' | ') AS variants,
  COUNT(*) AS events
FROM `your_project.analytics_123456789.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20260101' AND '20260923'
  AND collected_traffic_source.manual_campaign_name IS NOT NULL
GROUP BY normalised
HAVING spellings > 1
ORDER BY events DESC

Every row that comes back is one campaign your reports are splitting, with the variants listed so you can see exactly how. If your export predates the collected_traffic_source fields, the same idea works against whichever campaign fields your schema does carry. Check what you have before assuming the query will run.

Can I clean up what has already been collected?

Not in GA4. Collected values are not editable, and there is no merge or alias feature. What is in the interface for last quarter will say what it says for as long as the property exists.

Downstream you have more room. In BigQuery, normalise in the view rather than in every query:

CREATE OR REPLACE VIEW `your_project.analytics_clean.sessions` AS
SELECT
  * EXCEPT (collected_traffic_source),
  LOWER(TRIM(collected_traffic_source.manual_source))        AS source,
  LOWER(TRIM(collected_traffic_source.manual_medium))        AS medium,
  REPLACE(
    LOWER(TRIM(collected_traffic_source.manual_campaign_name)), '-', '_'
  ) AS campaign
FROM `your_project.analytics_123456789.events_*`

Lowercasing, trimming and settling on one separator will reunite most of the fragments on its own. The ones it misses are genuine synonyms, fb against facebook, where you need a small mapping table joined in rather than a string function. Keep that table somewhere your team can edit, because it will grow.

In Looker Studio you can do a narrower version of the same thing with a calculated field of LOWER(TRIM(Campaign)), which is worth knowing when BigQuery is not available yet. It is a patch rather than a fix: it lives in one data source, it has to be recreated in the next report, and it does nothing for the synonyms.

How do I stop it happening again?

Every durable version of this comes down to one idea: people should not be typing UTM parameters into a URL bar.

The moment a link is assembled by hand, the convention depends on memory and care, and it fails on the busy week rather than the quiet one. It also fails silently, because a malformed link still works. The visitor lands, the page loads, nothing complains. The damage appears a month later in a report nobody can explain.

So put a tool between the person and the link. Ours is at utmbuilder.vizionality.com, it is free, and the point of it is that source and medium come from a list rather than a keyboard, and the output is consistent regardless of who built it.

Whatever you use, the requirements are the same. Fixed choices for source and medium. Automatic lowercasing. One output everyone copies from. Somewhere the whole team can reach it, because the person who builds the link at eleven at night before a send is the person your convention has to survive.

Get that right and the reports stop needing interpretation. One campaign, one row, and a number you can actually act on.

FAQ

Are UTM parameters case sensitive?

Yes. GA4 stores the value exactly as it is sent and compares values character by character, so Facebook and facebook are recorded as two different sources and reported as two separate rows. Nothing merges them afterwards, which is why lowercasing everything is the single most valuable rule in a naming convention.

What is the difference between utm_source and utm_medium?

Source is where the click came from, such as google, facebook or mailchimp. Medium is what kind of traffic it is, such as cpc, email or social. Medium matters more than people expect, because GA4 derives your Channels report largely from it. An unrecognised medium can push traffic into Unassigned or the wrong channel entirely.

Why is my traffic showing as Unassigned in GA4?

Usually because the medium does not match any of the values GA4's channel rules recognise. A medium of newsletter rather than email, or a platform name in the medium field instead of the source field, leaves the session without a channel to fall into. Check your medium values against a short fixed vocabulary before looking for anything more complicated.

Can I merge duplicate campaign names in GA4?

No. Collected data cannot be edited and GA4 has no merge or alias feature for dimension values. You can normalise downstream by lowercasing and trimming in a BigQuery view, or with a calculated field in Looker Studio, but the underlying GA4 reports will keep showing the fragments.

What is the best UTM naming convention?

The one your team will actually follow. Lowercase everything so no judgement is required, fix a short vocabulary for source and medium, and settle on one separator and one campaign pattern such as season_initiative_year. Then build links with a tool rather than by hand, because a convention that depends on memory fails in the week everyone is busy.

Want help with Multi-Touch Attribution & UTM Persistence?

Source data that survives the whole journey, from first touch to signed contract, so the channels doing the early work get the credit.

More reading

Advanced Tracking

September 24, 2026 · 10 min read

Shopify's Meta CAPI Is Not Server-Side Tracking

Shopify really does send Conversions API events, and that still is not a server-side tagging layer. What the native channel cannot do, what owning the layer changes, and how to implement server-side GTM with Stape without double counting every order.

Read article
Advanced Tracking

September 23, 2026 · 9 min read

Basic or Advanced Consent Mode: What Each One Costs You

Both are Consent Mode, and they behave completely differently before a visitor chooses. What advanced mode really sends, what conversion modelling needs in return, and how to tell which one you are actually running.

Read article