Skip to main content
UTMBy SendURL team14 min read

UTM Naming Convention: Rules, Allowed Values and a Template

Part of the UTM handbook

A UTM naming convention is a written list of rules and allowed values that everyone tagging links follows. This one is built around GA4's channel rules: the values that work, the ones that break, and code to check them.

A UTM naming convention is a short, written set of rules and allowed values for utm_source, utm_medium, utm_campaign and utm_content, so that every link your team tags lands in the same report row and the right GA4 channel. The one below has seven rules, a list of mediums mapped to GA4's default channels, and code that rejects anything off the list.

It is part of the UTM parameters handbook, which explains what each parameter does. This page is about choosing the values.

The 7 rules

  1. Lowercase everything. GA4 reports Instagram and instagram as two sources.
  2. One separator: the hyphen. autumn-sale, never autumn_sale or autumn sale.
  3. utm_source is the site, app or sender the click comes from: instagram, newsletter, bing, ana-lopez.
  4. utm_medium is the channel type, picked from the allowed list below. Nothing else, ever.
  5. utm_campaign is <yyyy-mm>-<offer>: the launch month, then a short name. 2026-10-autumn-sale.
  6. utm_content is the placement or creative: bio-link-2, story-2026-10-14, hero-button.
  7. One owner keeps the list. A new source or medium goes on the list before it goes on a link.

Rule 1 is Google's own advice. Its URL builders page says "Parameter values are case sensitive, e.g. utm_source=google is different from utm_source=Google" and recommends lowercase "as a standard practice". GA4's channel rules ignore case ("Channel definitions aren't case sensitive"), so a capital letter splits your rows without changing the channel. That makes it easy to miss.

GA4 doesn't require rule 2; it's our pick. Hyphens win because two of the medium values in Google's channel rules use them (social-media, social-network) and they read cleanly in URLs. Google's own examples use underscores (spring_sale, paid_social), which is fine too. Mixing them is what hurts: paid-social and paid_social land in the same channel as two separate rows.

Allowed utm_medium values and their GA4 channel

The medium decides most of the channel. These values each match a rule in Google's default channel group (checked 15 October 2026):

utm_mediumGA4 default channelUse it for
emailEmailNewsletters, drips, transactional emails
socialOrganic SocialPosts, bio links, Stories, DMs, WhatsApp, community posts
paid-socialPaid Social (source on Google's social list), else Paid OtherAds on Meta, LinkedIn, TikTok, Pinterest, Reddit
cpcPaid Search (source on Google's search list), else Paid OtherManually tagged search ads, e.g. Microsoft Ads
displayDisplayBanner and native placements on publisher sites
videoOrganic VideoYouTube descriptions, Vimeo, Twitch
paid-videoPaid Video (source on Google's video list), else Paid OtherVideo ads not auto-tagged
smsSMSText messages
audioAudioPodcast show notes, audio ads
affiliateAffiliatesCreators and partners paid per sale
referralReferralPartner sites, directories, guest posts
pushMobile Push NotificationsApp and web push
qrUnassigned by defaultPosters, flyers, packaging. Add a custom channel for it (how)

qr is the one deliberate exception. GA4 has no print channel, so every accurate value for a poster lands in Unassigned. One agreed value plus a custom channel beats spreading print across referral and social.

Values that break

Each of these is a real-looking value with the channel it gets. Read from Google's rules, as of 15 October 2026:

ValueWhat happensUse instead
utm_medium=newsletterUnassigned (no rule mentions it), unless the source is on one of Google's listsemail
utm_medium=email-newsletterUnassigned (Email needs an exact match), unless the source is on one of Google's listsemail
utm_medium=organic-socialUnassigned unless the source is on the social listsocial
utm_medium=story, bio, postUnassigned unless the source is on the social listsocial + utm_content=story-…
utm_medium=influencerUnassigned, unless the source is on one of Google's listsaffiliate if paid per sale, else social
utm_medium=podcastUnassigned, unless the source is on one of Google's listsaudio
utm_medium=cost-per-clickNot paid (the paid pattern wants cp together): Organic Search with a search-engine source, Organic Social with a social one, else Unassignedcpc
utm_medium=social-paidNot paid: paid.* only matches at the startpaid-social
utm_medium=mobile-socialMatches Mobile Push Notifications (contains "mobile")social
utm_medium=cpm with a social sourceFits Paid Social, Display, Paid Other and Organic Social at oncepaid-social
utm_source=whatsapp + utm_medium=smsFits Organic Social and SMSwhatsapp + social

Note what isn't on the list: e-mail. Google's Email rule accepts email, e-mail, e_mail and e mail, so it works. It just splits your rows from email.

utm_source values for each network

The source matters most when the medium is paid: Paid Social needs a source on Google's list of social sites. That list is a PDF of sources and categories, dated 9 February 2023, with 592 social entries. We checked each value below against it:

Networkutm_sourceOn Google's list?Note
InstagraminstagramSocialGoogle's best-practice table suggests insta, which is not on the list
FacebookfacebookSocialfb is on the list too; pick one
TikToktiktokSocial
LinkedInlinkedinSocialPosts, first comments and colleagues' shares: LinkedIn link tracking
PinterestpinterestSocial
RedditredditSocial
WhatsAppwhatsappSocial
SnapchatsnapchatSocial
YouTubeyoutubeVideoPair with video or paid-video, not social
XxNoFine with social. For X ads use twitter, which is on the list
ThreadsthreadsNoFine with social; paid lands in Paid Other
BlueskybskyNoFine with social
TelegramtelegramNo (t.me is)Fine with social
DiscorddiscordNoFine with social
Emailyour list name: newsletter, onboarding-dripn/aThe medium email does the work
SMSyour list or sender: text-clubn/aThe medium sms does the work
Printthe object: poster, flyer, packn/aWith qr

Google doesn't say whether the social list is matched exactly or by substring, so stick to the plain names above.

Campaign names that sort

Use <yyyy-mm>-<offer>: 2026-10-autumn-sale, 2026-11-black-friday, 2027-01-pricing-change. Any list of campaigns then sorts by launch date, and two years of autumn sales never collide.

Two words to keep out of campaign names, because GA4 reads the campaign for two channels:

  • "cross-network" sends the session to the Cross-network channel.
  • "shop" or "shopping" can send it to Organic or Paid Shopping. The pattern is ^(.*(([^a-df-z]|^)shop|shopping).*)$: "shopping" anywhere, or "shop" at the start, after any character that isn't a letter (a hyphen, a digit, a dot) or after the letter e. 2026-11-shop-launch and eshop-tour match. 2026-11-workshop doesn't, because a k comes before "shop".

Keep the campaign name identical across every channel. The source and medium tell the channels apart; the campaign ties them together.

utm_content: name the placement

utm_content answers "which one?". Name the place or the creative, and keep the pattern fixed per channel:

ChannelPatternExamples
Emaillink positionhero-button, footer-link, ps-link
Bio linksslot numberbio-link-1, bio-link-2
Storiesstory-<yyyy-mm-dd>story-2026-10-14
Postspost typepinned-post, carousel, reel
Adsad name in the ad managercarousel-a, video-ad-1
Printlocationstore-window, market-stall, pack-insert

Lint your tags

A convention only holds if something checks it. For one link at a time, use the UTM builder that flags capitals and spaces and mediums that end in Unassigned. For a pipeline, these reject a tagged URL that's missing a required parameter, has a value that isn't lowercase-and-hyphens, or uses a medium off the list.

lint-utm.jsJavaScript
const ALLOWED_MEDIUMS = new Set([
  "email", "social", "paid-social", "cpc", "display", "video", "paid-video",
  "sms", "audio", "affiliate", "referral", "push", "qr",
]);
const VALUE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;

function lintUtm(url) {
  const params = new URL(url).searchParams;
  const problems = [];
  for (const key of ["utm_source", "utm_medium", "utm_campaign"]) {
    if (!params.get(key)) problems.push(`missing ${key}`);
  }
  for (const [key, value] of params) {
    if (key.startsWith("utm_") && !VALUE.test(value)) problems.push(`${key}="${value}" is not lowercase-and-hyphens`);
  }
  const medium = params.get("utm_medium");
  if (medium && !ALLOWED_MEDIUMS.has(medium)) problems.push(`utm_medium="${medium}" is not on the allowed list`);
  if (/(^|[^a-df-z])shop|shopping|cross-network/.test(params.get("utm_campaign") ?? "")) {
    problems.push("utm_campaign may trigger a Shopping or Cross-network channel");
  }
  return problems;
}

console.log(lintUtm("https://example.com/autumn-sale?utm_source=Instagram&utm_medium=story&utm_campaign=2026-10-autumn-sale").join("\n"));
// utm_source="Instagram" is not lowercase-and-hyphens
// utm_medium="story" is not on the allowed list

The same in Python, for a list of URLs in a file:

lint_utm.pyPython
import re
import sys
from urllib.parse import parse_qsl, urlsplit

ALLOWED_MEDIUMS = {"email", "social", "paid-social", "cpc", "display", "video", "paid-video",
                   "sms", "audio", "affiliate", "referral", "push", "qr"}
VALUE = re.compile(r"^[a-z0-9]+(?:-[a-z0-9]+)*$")


def lint_utm(url):
    params = dict(parse_qsl(urlsplit(url).query))
    problems = [f"missing {k}" for k in ("utm_source", "utm_medium", "utm_campaign") if not params.get(k)]
    problems += [f'{k}="{v}" is not lowercase-and-hyphens'
                 for k, v in params.items() if k.startswith("utm_") and not VALUE.match(v)]
    medium = params.get("utm_medium")
    if medium and medium not in ALLOWED_MEDIUMS:
        problems.append(f'utm_medium="{medium}" is not on the allowed list')
    if re.search(r"(^|[^a-df-z])shop|shopping|cross-network", params.get("utm_campaign", "")):
        problems.append("utm_campaign may trigger a Shopping or Cross-network channel")
    return problems


# Usage: python3 lint_utm.py urls.txt  (one URL per line)
for line in open(sys.argv[1]):
    url = line.strip()
    if url and (problems := lint_utm(url)):
        print(url, *problems, sep="\n  ")

In Google Sheets, a dropdown keeps people on the list in the first place. Put the allowed mediums in a range (say Lists!A2:A14), select the medium column, then Data → Data validation → Add rule → Dropdown (from a range) and choose Reject the input under the advanced options. For free-text columns, a check column flags bad values:

Flag a value that isn't lowercase-and-hyphensText
=IF(REGEXMATCH(B2, "^[a-z0-9]+(-[a-z0-9]+)*$"), "", "fix: " & B2)

Fix old messy data

GA4 can't rewrite values it has already collected. What it can do is group them differently. A custom channel group puts your own rules on top of the same data, and Google's custom channel groups page says they "can be applied to your reports retroactively".

Use one to send last year's newsletter and Newsletter mediums to Email, or to give qr its own channel. The steps are on the GA4 Unassigned guide. Then fix the links themselves, so the custom rules only have stragglers left to catch.

How the slug and the UTMs fit together

A short link has two names, and they do different jobs:

  • The slug is for people. snu.to/autumn-ig-bio2 can be read on a poster, typed from a podcast and recognised in a bio. It follows its own rules (1 to 32 letters, digits, - or _; case-sensitive; can't be changed later), covered in the short link slugs guide.
  • The UTMs are for GA4. They go on the destination URL the slug redirects to, following this convention.

Don't put UTM values in the slug or try to make them match. Keep one short link per placement and give it the UTMs of that placement; the short link then counts clicks per placement on its own, and GA4 gets clean source, medium and campaign values. The social media link tracking guide has a full sheet for a campaign set up that way.

The convention template

Paste this into a sheet (File → Import, or paste and use Split text to columns). The first block is the allowed-values list; the second is one campaign filled in.

utm-convention.csvText
list,value,ga4_channel,notes
medium,email,Email,
medium,social,Organic Social,
medium,paid-social,Paid Social,source must be on Google's social list
medium,cpc,Paid Search,source must be on Google's search list; Google Ads uses auto-tagging
medium,display,Display,
medium,video,Organic Video,
medium,paid-video,Paid Video,source must be on Google's video list
medium,sms,SMS,
medium,audio,Audio,
medium,affiliate,Affiliates,
medium,referral,Referral,
medium,push,Mobile Push Notifications,
medium,qr,Unassigned,custom channel group: medium exactly matches qr
source,instagram,,
source,facebook,,
source,tiktok,,
source,linkedin,,
source,youtube,,
source,x,,use twitter for X ads
source,newsletter,,
source,poster,,
campaign-2026-10-autumn-sale.csvText
placement,short_link,utm_source,utm_medium,utm_campaign,utm_content
Instagram bio link 2,snu.to/autumn-ig-bio2,instagram,social,2026-10-autumn-sale,bio-link-2
Instagram Story 14 Oct,snu.to/autumn-ig-story,instagram,social,2026-10-autumn-sale,story-2026-10-14
Newsletter hero button,snu.to/autumn-news-hero,newsletter,email,2026-10-autumn-sale,hero-button
Meta carousel ad,(none: tagged URL in the ad),facebook,paid-social,2026-10-autumn-sale,carousel-a
Store window poster,snu.to/autumn-poster,poster,qr,2026-10-autumn-sale,store-window
Podcast episode 112,snu.to/autumn-pod,making-it-podcast,audio,2026-10-autumn-sale,ep-112-show-notes

Build the tagged URLs from the second block with the Sheets formula in the UTM parameters guide, or paste the destinations into the bulk UTM builder, which runs the same capital, space and channel checks on every row.

Checklist for adopting it

  • The allowed utm_medium list is written down and one person owns it
  • Every value is lowercase, words joined with hyphens
  • Each network has exactly one utm_source value
  • Campaign names start with yyyy-mm and avoid "shop" and "cross-network"
  • utm_content follows one pattern per channel
  • The medium column in your sheet is a dropdown, and the lint runs before launch
  • qr has a custom channel in GA4, and old messy values are grouped there too
  • UTMs go on the destination, never in the short link's slug

Frequently asked questions

Should UTMs be lowercase?

Yes. GA4 treats utm_source=Instagram and utm_source=instagram as two different sources in reports, and Google's own best-practice table says to use lowercase as a standard practice.

Hyphens or underscores in UTMs?

Either works in GA4. Pick one and never mix them, because paid-social and paid_social become two report rows. Hyphens match the medium values in Google's own channel rules (social-media, social-network), so this guide uses hyphens.

What should utm_medium be for Instagram?

social for organic posts, bio links and Stories, and paid-social for ads, with utm_source=instagram. Both land in the channel you expect: Organic Social and Paid Social.

How do you name UTM campaigns?

Start with the year and month the campaign launches, then a short name for the offer: 2026-10-autumn-sale. The names sort by date, and the same campaign keeps the same name on every channel.

Start tracking links free.

Create your first short link with click analytics in seconds. No credit card, no sign-up to start.

Shorten a link