utm.new
guide ·

Tracking AI-generated ad campaigns without breaking your UTMs

When you spin up hundreds of AI-written ad variants, structured utm_content and utm_term plus platform macros for utm_id keep every creative comparable.

AI changed the math on ad creative. Where a team once shipped four variants of an ad, it now ships forty — different hooks, headlines, angles, all generated in an afternoon. The creative side scaled. The tagging side usually didn’t, and that’s where the reports fall apart.

The failure mode is predictable. Each variant gets a slightly different free-text utm_content, nobody agreed on a format, and three weeks later you can’t answer a basic question: which hook won? The data is there, scattered across hook-a, Hook_A, hookA-final, and new-hook-test-2. At ten variants you can eyeball it. At three hundred you can’t.

Volume doesn’t break UTMs. Unstructured volume breaks UTMs. Here’s how to keep hundreds of AI-generated variants comparable.

Structure beats free text

The instinct with utm_content is to describe the creative in words. That works until you want to slice the data by one dimension — say, “all the variants using the loss-aversion angle.” Free text can’t be sliced; structured fields can.

Treat utm_content as a small set of positional slots, not a sentence. Pick the dimensions you actually compare creatives on and encode each as a slot, separated by a consistent character:

utm_content = {angle}-{format}-{hook}-{version}

So a single ad becomes utm_content=loss-aversion-video-15s-free-trial-hook-v3. Every variant follows the same shape. Now “all loss-aversion variants” is a filter on the first slot, “all 15-second video” is a filter on the second, and you can compare across the dimension you care about instead of reading three hundred unique strings.

utm_term carries the other axis. For paid search, that’s still the keyword. For social, use it for the ad set or audience: utm_term=lookalike-purchasers-us. Keep one dimension per parameter and you can pivot the report any way you need.

The rule that makes this hold: lowercase, hyphen-separated values, every time. utm_content=Video_Hook and utm_content=video-hook are two different rows in GA4 — case-sensitive and split. This is the single most common way AI-generated campaigns fragment their own data, because the generator doesn’t enforce casing unless you tell it to. The broader rationale lives in UTM parameters best practices.

Let the platform fill in utm_id

Do not hand-write campaign IDs into URLs at this volume. You will fat-finger one, copy-paste a stale value, and quietly merge two campaigns in your reports.

Every serious ad platform substitutes a real ID at click time through a macro. Put the macro in utm_id and the platform fills it in:

Platformutm_id macro
Google Ads{campaignid}
Meta{{campaign.id}}
Microsoft Ads{CampaignId}
TikTok__CAMPAIGN_ID__

So your URL ships with utm_id={campaignid} on Google Ads or utm_id={{campaign.id}} on Meta, and the platform writes the actual ID at click time. This matters beyond convenience: GA4 uses utm_id to join your campaign back to imported ad-cost data, so a correct utm_id is what makes cost-per-result reporting work. Hard-code it and you’re maintaining a mapping by hand; use the macro and it maintains itself.

Note the curly braces and underscores in those macros. A builder that blindly runs your URL through URLSearchParams will percent-encode them — %7Bcampaignid%7D — and the platform won’t recognize the token. Use a builder that preserves token markers (the utm.new builder does), or assemble those URLs by hand.

Generate the taxonomy with AI, enforce it with a builder

The same tools writing your ad copy can write your tagging plan — if you constrain them. Don’t ask an LLM to “tag these ads.” Ask it to emit values that fit a fixed schema you defined, lowercase and hyphenated, choosing from an allowed list for each slot.

A prompt that works looks like: “For each ad below, output utm_content in the format {angle}-{format}-{hook}-{version}. angle must be one of: loss-aversion, social-proof, curiosity. format must be one of: static, video-15s, video-30s, carousel. All values lowercase and hyphen-separated. Return a table.”

That keeps the model inside your taxonomy instead of inventing a new label per ad. The deeper pattern — using AI to design and police the convention — is its own discipline; see Use AI to generate and govern your UTM taxonomy.

But AI proposes; it doesn’t enforce. A model will happily output Loss_Aversion on row 47 because the input nudged it. The enforcement layer is a builder or a validation step that rejects anything off-schema before the URL ships. Generate at scale, validate at scale, then publish. The convention itself — which slots, which allowed values — is the thing to nail down first; a UTM convention for the AI search era covers how to think about that.

A worked example

Say you generate thirty Meta variants across three angles, two formats, and five hooks. Your tagging plan is:

utm_source   = meta
utm_medium   = paid-social
utm_campaign = spring-launch-2026        (stable for the whole flight)
utm_id       = {{campaign.id}}           (Meta fills this at click time)
utm_term     = lookalike-purchasers-us   (the audience)
utm_content  = {angle}-{format}-{hook}-v1

Every one of the thirty URLs differs only in utm_content, and that field is structured. When the campaign closes, you pivot on angle to find the winning message, on format to find whether video beat static, and on hook to rank the openers — all from one consistent set of values. The thirty-first variant you add next week slots right in.

Keep utm_campaign stable for the entire flight. Renaming it mid-campaign to spring-launch-2026-v2 splits the report exactly when you want a clean read. Variation belongs in utm_content and utm_term — that’s what they’re for.

The short version

  • Structure utm_content as fixed slots, not free text, so creatives stay sliceable.
  • Keep one comparison dimension per parameter; put the audience or keyword in utm_term.
  • Lowercase and hyphenate everything — casing drift is how AI campaigns fragment their own data.
  • Use the platform’s macro for utm_id; never hand-write campaign IDs at volume.
  • Let AI generate values inside a fixed schema, then validate before publishing.

Do that and three hundred AI-written variants report as cleanly as three.

More in this series