Ad Platform Names
Spend leaves the ad platform with its names and without its UTMs, so the campaign name is the key that joins spend to revenue: how the join works, where it runs, what each level of a name carries, and how far to trust the platform's own numbers.
On this page
Your links are in order: one closed list of sources and mediums, one campaign pattern, every link clicked where it’s published before anyone trusts it. Then you open Google Ads.
| Campaign |
|---|
Spring Sale - NEW |
spring_sale_v2_FINAL |
test_campaign_dont_delete |
Three names, three typists, three moods. And somebody is still afraid to delete the third one.
These names already reach your reports. Chapter 5 showed how: on a Google Ads click with auto-tagging on, GA4 takes the campaign from Google Ads, so these are the campaign names your report shows.
So the names leak out one way already. This chapter follows the other way: the money. Stride’s spring sale launched with an ad on Meta, so that’s where we’ll follow it. The mechanism is the same on every platform.
Most teams treat the names inside an ad platform as housekeeping. The links get governed; the campaign name is a label for whoever runs the account, typed however reads well that day. And when someone asks what a campaign returned, the platform’s own dashboard has a number ready.
So spend sits in one table and revenue in another. The only view of what each platform’s money returned is each platform’s report on itself, and those reports don’t compare with each other. Ask which platform worked hardest and you get one answer per platform, each in its own favor.
Meanwhile the name has a job nobody gave it.
The spend leaves without its UTMs
Every paid campaign is written down twice, by two systems, for two different reasons.
The ad platform keeps one record. It logs spend against its own objects: a row per campaign, ad set or ad, for each day or whatever range you ask for, each with an ID and a name. It counts impressions, and clicks, including clicks that never become a visit. Pull that record out as a file, an export, and these are its columns (Meta’s API field names), one per line:
campaign_id
campaign_name
adset_name
ad_name
spend
impressions
clicks
Look for utm_campaign in that list. It isn’t there.
Analytics keeps the other record: visits, orders and revenue, filed under the values each click carried. Its campaign column is utm_campaign, and on its own it doesn’t know what the Meta ad cost.
The spend rows carry no UTM columns. The UTMs are a setting on the ad (Meta keeps them on the ad’s creative, Google Ads in its tracking settings), filled in when someone clicks. They’re a fact about the click, not about the money.
So the two records share one thing: the campaign’s name. In Meta it’s a free-text field, and it’s the envelope from the Introduction, opened by someone else. An analyst, a spreadsheet, a warehouse query: none of them can ask what you meant.
A join matches strings, not intentions
Here’s what a careless name costs, as a what-if. It never happened to Stride.
Suppose Stride’s Meta campaign had been named the way your Google Ads screen is: Spring Sale - NEW. And suppose the ad’s links had been typed from Dana’s list instead of filled in from the name, so every one of them carries spring_sale-runners-2025_03. The links are perfect.
Two small tables for the sale’s first week. The amounts are example numbers.
The Meta export
| Campaign | Spend |
|---|---|
Spring Sale - NEW | $2,400 |
GA4, visits from Meta’s ad (facebook / paid_social)
| Campaign | Revenue |
|---|---|
spring_sale-runners-2025_03 | $8,400 |
The join matches on two things: the campaign, and the platform. Meta’s spend pairs only with visits from Meta’s own ads. Here that’s facebook / paid_social, because the what-if’s ad runs on Facebook; had the same Meta campaign also run on Instagram, its instagram / paid_social rows would belong on Meta’s side too. Without the platform, the newsletter’s numbers for the same campaign would land in Meta’s column.
Now line the two tables up on the campaign column and work out the return on ad spend (ROAS: revenue divided by spend). What did Meta’s $2,400 bring back?
Show the joined tableHide it
| Campaign | Spend / revenue |
|---|---|
Spring Sale - NEW | $2,400 / none |
spring_sale-runners-2025_03 | none / $8,400 |
Two rows, and both are wrong. Meta’s row reads a ROAS of 0: its $2,400 bought nothing. The other row has no spend to divide by: 60 orders worth $8,400 cost nothing at all.
Lining up rows from two tables on a column that holds the same value in both is a join. The column is the join key, here paired with the platform. In a warehouse the match is Chapter 3’s exact string rule, now between two systems: character for character, capitals and spaces included. Spring Sale - NEW and spring_sale-runners-2025_03 don’t match, and neither would Spring_sale-runners-2025_03, one capital letter away. A spreadsheet lookup forgives capitals but not a space or a _v2, so it hides that one mismatch until the join moves to a warehouse.
A failed join raises no error. It leaves two orphan rows, spend with no revenue and revenue with no spend, and a dashboard that sums them reports both with a straight face.
Now rename the Meta campaign spring_sale-runners-2025_03. Change nothing else. Not one link, not one setting in GA4.
The next export joins into one row:
| Campaign | Spend / revenue |
|---|---|
spring_sale-runners-2025_03 | $2,400 / $8,400 |
$8,400 divided by $2,400 is a ROAS of 3.5: every dollar on Meta brought back three and a half.
The links never changed, and they were right all along.
The fix was the name.
One caution: exports you pulled before the rename keep the old name. The rename fixes the next export, not the history already sitting in a file.
Ad platform export
Meta, the sale’s first week
- campaign_name (join key)
- ad_name
- spend
- impressions
- clicks
campaign_name and utm_campaign both hold
spring_sale-
Web analytics
GA4
- utm_source
- utm_medium
- utm_campaign (join key)
- sessions
- revenue
Where the join runs
A join is a step someone, or something, has to run. The range has two ends.
By hand. Download the platform’s export and the analytics report, paste both into a spreadsheet, and match the campaign columns with a lookup formula. It works. It’s also a chore you redo every time someone asks, once per platform, and its answer is only as fresh as the last time you did it.
In a data warehouse. A data warehouse is a database built to hold every system’s data in one place, where it lands on a schedule: the ad platforms’ spend, analytics’ visits and revenue, and later the CRM’s leads. It’s stage 5 on the tool map; BigQuery and Snowflake are two. There the join is a query that runs every day, across every platform, and nobody downloads a file.
What a warehouse buys is repetition. A team needs one when the join has to run again and again, across several platforms, or soon with a third table of leads beside the other two. A single platform checked once a quarter fits in a spreadsheet.
Between those ends sit other places the join can run, each with its own cost. Chapter 7 maps them.
An aside for the expert who has been waiting to object: there’s another key. Google Ads, for one, can write its own campaign ID onto the link with {campaignid}. An ID is never retyped, recapitalized or renamed, so for joining Google Ads spend to Google Ads clicks it’s exact.
It’s also Chapter 4’s Opaque ID, with the twist that the platform holds the lookup table. The ID carries none of your dimensions (no initiative, no product, no launch month) until you look it up in Google’s data. And the newsletter and the QR code for the same sale have no Google Ads ID at all.
The name is the key that lines up every channel, because it’s the one value you use everywhere.
Three levels, three decisions
Ad platforms stack their ads in three levels. Google Ads calls the middle one an ad group; Meta calls it an ad set. Each level records a different decision, and each has a UTM that does the same job on a link:
| Level (Google / Meta) | Records | On a link |
|---|---|---|
| campaign | the initiative | utm_campaign |
| ad group / ad set | who the ads reach | utm_term |
| ad | the creative | utm_content |
One name at each level, on Meta:
| Level | Example |
|---|---|
| campaign | spring_sale-runners-2025_03 |
| ad set | retargeting-cart_abandoners |
| ad | video-testimonial-15sec |
The top row’s value is the same string in both places: that’s the join key. The middle row’s example is an illustration, the kind of audience value utm_term carries. On Meta, the ad set is also where schedule and targeting are set, and usually the budget.
Only the top row is needed for the join. How much of the taxonomy the other two carry is a choice, with a price on each side:
- The campaign alone. Spend lines up with revenue by initiative, on every platform. Ad sets and ads keep whatever names their makers gave them, and nobody can ask about them across platforms.
- Patterned ad sets and ads as well. Now one query can read every level on every platform and answer what no single platform’s report can. Do video ads beat static ones everywhere, or only on Meta? Does a retargeting audience return more per dollar on Meta or on Google Ads? The price is a pattern per level, and three names to get right on every launch instead of one.
Decide level by level, by the questions you’ll ask across platforms. Looking thorough isn’t one of them.
Whatever you pattern, use Chapter 3’s delimiters: hyphens between segments, underscores inside one. Then one parser reads every level.
The platform is a column, never a prefix
Someone will propose putting the platform in front, so every row says where it came from:
facebook-spring_sale-runners-2025_03
The instinct is sound. The place is wrong.
The same sale runs in the newsletter and on a QR code, and every one of them carries spring_sale-runners-2025_03; so would a Google Ads campaign for it. Prefix the Meta name and it stops matching all of them. A key is shared only while the value is identical.
The platform still has to travel with every row. It has a better home:
- One table per platform says where its rows came from by being that table.
- Once exports are stacked into one table, add a
platformcolumn as each export loads.
Every row explains itself, and the name stays the shared key. The column is also the second half of the match: it pairs each platform’s spend with the visits its own ads brought, Meta’s with facebook and instagram rows of paid_social, never the newsletter’s.
How far to trust the platform’s own numbers
Chapter 1 promised the platform’s own data would be put to work here. It can be, within limits that come straight from how it’s collected.
An ad platform tracks conversions for itself, usually with a pixel: a tag on your site that reports each sale back to the platform. The platform then credits the sale to its own ad if the buyer clicked that ad, or on some platforms only saw it, within a window of days set in the platform.
Two things follow:
- Each platform sees only its own ads. Meta doesn’t know the buyer also clicked a Google search ad, and Google Ads doesn’t know about the Facebook ad.
- So two platforms can each claim the same order. Add up what they report and the total can come to more than you sold.
Ask two platforms who sold the order, and both can raise a hand.
Inside one platform, the pixel is sharp: which ad, which ad set, which creative, compared under the same rules.
Across platforms it can’t settle anything, because every platform counts by its own rules and credits itself. Comparing them takes one record that doesn’t belong to any of them: your own revenue, counted once, joined to each platform’s spend by name.
That’s the join from earlier in this chapter. Which touch deserves the credit when a buyer met several ads is a different question, and Chapter 11’s.
Your situation
Four questions place you.
How many platforms do you buy ads on?
- One platform: the campaign name is the whole job, and a spreadsheet can run the join.
- Several: the names have to match each other as well as the links, and the join repeats once per platform.
Does anyone join spend to revenue today?
- Nobody: each platform’s own report is the only view of its return, and those don’t compare. A matching campaign name is what makes a first join possible.
- A spreadsheet: the names decide how much of each export matches before someone fixes rows by hand.
- A warehouse: a mismatched name is an orphan row every day until someone fixes the name.
Do you compare audiences or creative across platforms?
- No: the campaign level carries the join, and ad sets and ads can stay as they are.
- Yes: pattern them too, so one parser reads every level on every platform.
Can the platform fill the link from the name?
- Yes, as Meta does with
{{campaign.name}}: the link copies the name. - No, as on Google Ads, which has no such value for the campaign name: the name and the link’s
utm_campaignare two values someone keeps identical. With auto-tagging on, GA4 already shows the Google Ads name (Chapter 5), so spend joins to GA4 either way; the shared value is what lines Google Ads up with the other channels, and with the CRM in Chapter 7.
What changes
| The decision | When the names are housekeeping | When the name is the join key |
|---|---|---|
| The campaign name in the platform | whatever reads well; only the links are governed | the same value as utm_campaign, character for character |
| Comparing platforms | each platform’s own reported return | each platform’s spend joined to your own revenue; its own report compares ads inside it |
| Where the platform goes | a prefix in the name, so rows say where they came from | a platform column, added as each export loads |
| Ad set and ad names | by habit, or patterned everywhere because it looks thorough | as much of the taxonomy as the questions you’ll ask across platforms |
| Spend and revenue don’t line up | check the links | check the names first: a rename fixes the next export without touching a link |
Where the rule lives
A platform’s name field takes any text: capitals, spaces, _v2_FINAL. So the naming rule sits in one of two places:
Beside the path
Written in a naming document, it’s one more thing to remember in a free-text field, and the name and the link are two values to keep identical by hand.
Inside the path
When the name is made from the same list as the links, or the link is filled from the name, there’s one name to get right.
Check yourself
A join of Meta spend to GA4 revenue drops a third of the spend. What do you check first, and what fixes it?
Show answerHide answer
Whether the Meta campaign names and the utm_campaign values match exactly. A _v2 or a stray space breaks any join; one capital letter breaks a warehouse join, while a spreadsheet lookup lets it pass. Where the links were typed, rename the campaign inside Meta to the links’ value, and the next export joins. Filling utm_campaign from {{campaign.name}} makes the two match by construction.
A colleague wants every Meta campaign name to start with facebook-, so warehouse rows say where they came from. What breaks, and what does the job instead?
Show answerHide answer
The key. With the prefix, the Meta name no longer matches the value every other channel uses for the same sale, Google Ads and the newsletter included, so those rows stop lining up. A platform column, added as each export loads, says where each row came from and leaves the name alone.
Meta's report and Google Ads' report together claim more sales than your store recorded. Which one is wrong, and what can you compare across them?
Show answerHide answer
Possibly neither. Each credits the sales its own ads touched, and both can claim the same order. Use each report to compare ads inside its own platform; compare platforms by joining each one’s spend to your own revenue on the campaign name.
In one line
Spend leaves the ad platform with its names and without its UTMs, so the campaign name inside the platform must be the same value as utm_campaign. It's the key that joins spend to revenue.
Before the next chapter
Export last month's campaign names from your biggest ad platform and count how many match a utm_campaign value exactly.