Pixels · marketers · PMs · engineers
UTM is not a click id
utm_source, utm_medium, utm_campaign, utm_content, and utm_term are a convention Google Analytics popularized. Ads platforms have their own ids, and those ids are what CAPI and auto-tagging join on. A redirect that strips gclid to keep the URL pretty is an attribution outage with a clean address bar.
What UTMs are for
GA4 reads UTMs into session source and campaign dimensions. Your warehouse reads them off the landing URL or off the collect hit. They make reports readable: facebook vs email vs partner. They are not a contract with Meta, Google Ads, or TikTok. facebook.com/tr does not look at utm_source=meta. /g/collect will store UTMs as campaign fields and still needs gclid for Ads auto-tagging.
Five names, lowercase, underscore. utm_source, utm_medium, utm_campaign, utm_content, utm_term. Anything else (source, Source, utm-source) is a custom param your GA4 property will not map the same way. A sixth UTM you invented is a query param. Treat it as such in joins.
gclid, fbclid, ttclid
gclid is Google Ads click id, stamped by auto-tagging on the landing URL. fbclid is Meta's. ttclid is TikTok's. MMPs have their own. These are the keys CAPI, enhanced conversions, and offline uploads join on. They are long opaque tokens, not human campaign names. They belong in the query, then in a first-party cookie (_gcl_aw, _fbc), then on the conversion payload.
A landing URL with utm_source=google and no gclid is a campaign label without a click join. Auto-tagging off, a redirect that dropped gclid, or a vanity short link that only forwarded UTMs. Paid search will under-claim. UTM reports will still look tidy. Keep both on the URL until you have stored the click id yourself.
Casing and duplicates
utm_source=Meta and utm_source=meta are two sources. utm_medium=CPC and utm_medium=cpc are two mediums. Pick a casing policy (all lowercase is the usual) and enforce it in the ad tool, the email ESP, and the QR generator. Do not fix it in a pixel. The pixel forwards what the URL had.
Two UTM keys on one URL, from a stacked email-plus-ads tag or a duplicate GTM URL builder, make last-click look random. Last writer wins in the address bar; last-click in GA4 follows session rules that are not last writer. Pick a last-writer rule at the redirect layer. Strip leftover UTMs from internal links so a /thank-you?utm_source=email does not reattribute a paid session.
Do not invent click ids as UTMs
Putting the gclid in utm_content does not fill gclid. Enhanced conversions and Google Ads will not see it. Putting fbclid in utm_campaign does not fill fbc. Send the real parameter, and send it on the conversion as well as the landing hit if your tag reads the URL only once. SPA route changes that drop the query will lose the click id unless you already copied it into storage.
ValueTrack {campaignid} in a UTM is a campaign id, still not gclid. Useful for reporting. Useless as a click join. Do not disable auto-tagging because UTMs look cleaner. You can have auto-tagging and UTMs on the same URL. The ugly gclid is the one that pays.
Pixels that ignore UTMs on purpose
Floodlight, Meta browser pixel, and Google Ads conversion image do not require UTMs. A Floodlight tag wants src, type, cat, ord. A Meta Purchase wants id, ev, cd[value], cd[currency]. A Google Ads image wants conversion_id in the path, label, guid=ON, script=0. Adding UTMs to those URLs does not fix attribution; it lengthens the GET. Put UTMs on the landing page URL, not on the 1x1.
GA4 collect will happily carry utm_* if they are on the page URL when the hit fires. If the SPA stripped them before en=page_view, GA4 never sees them. That looks like a UTM policy failure and is a history-hook failure. Store UTMs on first landing the same way you store gclid.
Check the artifact
Paste the pixel URL or JSON body into the
playground. Same engine as
pixellint validate. Nothing leaves the tab.