UTM Parameters, Explained: A Practical Guide to Campaign Tracking

10 min readanalytics, marketing

UTM parameters are the small query-string tags — utm_source, utm_medium, utm_campaign and friends — that you append to a URL so your analytics tool knows where a click came from. They are the difference between a report that says a signup came from “direct” and one that says it came from the August newsletter, hero button, second send.

By the end of this post you will know what each parameter is for, including the GA4-era additions like utm_id and utm_source_platform; how GA4 turns them into the session and first-user dimensions you see in reports; and the naming conventions that stop those reports from fragmenting into near-duplicate rows. We will also cover the mistakes that quietly corrupt campaign data — tagging internal links, hand-tagging organic traffic, losing parameters in redirects — and a lightweight workflow that keeps a small team consistent.

None of this requires special tooling. UTMs are plain text appended to URLs. The hard part is discipline, and discipline is mostly a matter of writing the rules down once.

How UTM parameters actually work

There is nothing magic about a UTM parameter. It is an ordinary key-value pair in the query component of a URL, exactly as described in RFC 3986. Browsers do not treat it specially, and neither does the destination server unless you write code that reads it. The name comes from the Urchin Tracking Module — Urchin being the analytics product Google acquired and turned into Google Analytics — and the prefix stuck.

When a tagged link is clicked, the landing page loads with the parameters still in the address bar. The analytics snippet on that page reads them out of the URL (the same way your own code would with URLSearchParams) and records them with the page view. That has one important implication: attribution happens client-side, on the first page load. If anything strips the parameters before that page loads — a redirect, a broken shortlink, an app’s in-app browser rewriting the URL — the data is simply gone. There is no server-side registry to recover it from.

The five classic parameters

ParameterQuestion it answersExample values
utm_sourceWhich property sent the click?newsletter, google, linkedin
utm_mediumWhat kind of channel is it?email, cpc, social
utm_campaignWhich initiative or push?2026-08-launch, black-friday
utm_termWhich paid keyword?url-shortener
utm_contentWhich link or creative variant?hero-cta, footer-link

A fully tagged URL looks like this (line breaks for readability):

https://example.com/pricing
  ?utm_source=newsletter
  &utm_medium=email
  &utm_campaign=2026-08-launch
  &utm_content=hero-cta

The distinction people get wrong most often is source versus medium. Medium is the channel category and should come from a small, closed vocabulary: email, cpc, social, display, referral, and a handful of others. Source is the specific property within that channel: the newsletter, the site, the platform. This matters more than it looks, because GA4 derives its channel groupings largely from the source and medium pair — put the wrong word in utm_medium and the traffic lands in the wrong channel bucket in every report.

utm_term and utm_content are optional and situational: utm_term exists mainly for manually tagged paid-search keywords, and utm_content is the right place for anything that varies within a campaign — the A/B variant, the button versus the footer link — so that utm_campaign can stay stable.

The GA4-era additions: utm_id and friends

GA4 recognizes several parameters beyond the classic five, documented in Google’s guide to collecting campaign data with custom URLs. The two worth knowing:

Google also documents utm_creative_format and utm_marketing_tactic, but their support in standard reports has historically lagged the core parameters — check the current documentation before you build a convention on top of them. For most small teams, the five classics plus utm_id (only if you do cost imports) is the complete set.

How GA4 maps UTMs to its reporting dimensions

GA4 does not report on utm_source directly. It collects the full landing URL, extracts the parameters, and materializes them into traffic-source dimensions at different scopes:

On top of these, GA4 computes a default channel group — Organic Search, Paid Social, Email, and so on — from the source, medium, and campaign values according to published classification rules. The rules match on exact strings: utm_medium=email goes to the Email channel, while utm_medium=e-mail historically needed Google’s tolerance for variants and utm_medium=Newsletter can fall through to Unassigned. This is the mechanical reason naming conventions matter: channel grouping is string matching, and your strings are the input.

One special case: Google Ads. With auto-tagging enabled, Google appends a gclid parameter that carries the campaign, ad group, keyword, and more — richer data than manual UTMs can express, which is why the general advice is to rely on auto-tagging for Google Ads and reserve manual UTMs for everything else. I cover how GCLID and auto-tagging actually work in a separate post; the short version is that hand-tagging your Google Ads final URLs is usually redundant and occasionally conflicting.

Naming conventions that keep reports clean

Every distinct string becomes a distinct row. Email, email, and E-Mail are three different mediums as far as GA4 is concerned; spring_sale and spring-sale are two campaigns. Nothing merges them for you. The symptom is a campaign report where the real total is split across four near-identical rows, filters silently miss half the data, and channel grouping misclassifies whatever fell outside the exact strings it matches. The conventions that prevent this are boring and absolute:

A convention small enough to memorize:

utm_source    the specific property    newsletter, google, linkedin
utm_medium    closed list only:        email, social, paid-social,
                                       cpc, display, qr, partner
utm_campaign  yyyy-mm-slug             2026-08-launch
utm_content   variant or placement     hero-cta, footer-link

Mistakes that quietly corrupt your data

Tagging internal links

UTMs describe how a visitor arrived at your site. Put them on a link from your homepage to your pricing page and you overwrite the real acquisition data with a story about your own navigation — the visitor who came from a paid ad now appears to have come from utm_source=homepage. Internal links must never carry UTM parameters. If you need to measure internal promotion, use event tracking, not campaign tagging.

Hand-tagging traffic the analytics tool already classifies

You cannot tag organic search results, and you should not try to simulate them: GA4 detects organic and referral traffic from the referrer on its own. Manual tags are for links you place — emails, paid placements on non-Google platforms, social profiles, QR codes, partner swaps — not for reproducing classifications the tool makes better without your help.

Losing parameters in redirects

Whether a redirect preserves the query string has nothing to do with its status code and everything to do with how the redirect was implemented — the Location header contains whatever URL the server put there. Legacy rewrite rules, naive shorteners, and some CMS redirect plugins drop the query string entirely, which silently converts your tagged campaign traffic into direct traffic. (The status code still matters for other reasons; see 301 vs 302 redirects and when to use each.) The fix is procedural: before a campaign ships, click a tagged link through every redirect in its path and confirm the parameters survive to the final address bar.

Over-tagging

Tagging every link everywhere produces noise, not insight. If a value will never change a decision, do not collect it. A tag set you can audit in an afternoon is worth more than an exhaustive one nobody trusts.

A workflow that keeps a small team consistent

Conventions decay without a system, and the system can be very small. Two patterns work well:

A shared spreadsheet as the source of truth. One row per tagged link: destination, source, medium, campaign, content, and a formula column that assembles the final URL. Use dropdown validation on the medium column so the closed vocabulary is enforced by the sheet, not by memory. Nobody constructs a tagged URL by hand; they add a row and copy the output. This costs nothing and fixes the majority of consistency problems, because most bad UTMs are typed in a hurry.

A link shortener as the source of truth. The upgrade from the spreadsheet: store the destination and its UTM values together in a database, and share a clean short URL instead of a 600-character tagged one. You get links that fit in a QR code or a social bio, click counts independent of the destination’s analytics, and — the real win — tagging that lives in exactly one place, so fixing a bad tag means editing one row instead of chasing down every pasted copy. If you want to build one, I wrote up the full Next.js and Supabase setup separately.

As an aside, that is how this site works: each shortlink on this site can store a set of UTM values that get appended to the destination at redirect time. Incoming query parameters on the short URL are forwarded to the destination when forwarding is enabled for that link — and even when it is not, ad-click identifiers like gclid are preserved so conversion attribution survives. In every case, parameters already present on the destination URL win over appended ones, so a deliberately tagged destination is never clobbered by the link’s defaults. That merge logic is about thirty lines of code; the value is that the tagging decision is made once, when the link is created, instead of every time someone shares it.

Whichever system you pick, close the loop: once a month, open the session source and medium report, sort by sessions, and scan for values that violate the convention. Every garbage value traces back to an untracked link somewhere — fix it at the source and the report stays trustworthy.

Bottom line

UTM parameters are the simplest part of the analytics stack: plain query strings, read once on the landing page, reported as-is. Use utm_source for the property, utm_medium from a closed vocabulary aligned with GA4’s channel rules, utm_campaign for the initiative, and utm_content for the variant. Add utm_id only if you import cost data. Lowercase everything, ban spaces, never tag internal links, let auto-tagging handle Google Ads, and test that your redirects preserve the query string. Then put the whole thing behind a spreadsheet or a shortener so the convention is enforced by a system instead of by vigilance. Clean campaign data is not a tooling problem — it is a naming problem, and naming problems are solved by writing the names down.

If you want the conventions applied for you, the free UTM builder on this site lowercases values and converts spaces as you type — no signup, runs entirely in the browser.