You uploaded your Meta and TikTok costs into GA4, everything looked fine, and yet your non-Google ROAS shows zeros or numbers that make no sense. You didn’t break anything: your GA4 campaign data import is failing silently, and until recently you had almost no way to find out why. Since August 10, 2026, that’s no longer true. Google shipped the Campaign data import validation report, which finally exposes, campaign by campaign, whether the money you uploaded actually attached itself to measured sessions. This guide teaches you to read that report, to understand why joins break, and to fix your UTM taxonomy so your budget decisions rest on real numbers.
A useful warning first: almost every tutorial still ranking on this topic predates the November 2025 rename and ignores the three summer 2026 changes. They show you how to set up an import. That’s not your problem. Your problem is that it’s set up and it isn’t joining. So let’s approach this through diagnosis, not setup.
What campaign data import actually does
Start with the mechanism, because 90% of the mistakes come from a wrong mental model of what happens under the hood. When you upload a cost file, GA4 does not reprocess your historical data and rewrites nothing. The join happens at query time: when you open a report, GA4 tries to match each imported cost row to sessions already collected, using three mandatory keys: utm_source, utm_medium and date. If those three keys don’t match exactly what exists in your sessions, the cost row stays orphaned. It’s in GA4, it shows up nowhere.
The name changed in November 2025 (the old “cost data import” became “campaign data import”), but the principle is identical. Above all, remember this: the import does not create sessions, it tries to stick costs onto sessions that already exist. Everything else in this article follows from that one sentence.
To place this piece inside the 2026 ecosystem, keep in mind that it directly feeds GA4 cross-channel budgeting: its projections consume your cost data. A broken import means wrong projections. We’ll come back to that at the end.
The 3 summer 2026 changes that break existing imports
If your import worked in the spring and doesn’t anymore, it’s probably not a coincidence. Three recent changes altered how the feature behaves:
July 28, 2026, currency becomes mandatory. Any import carrying cost figures must now specify a currency in ISO 4217 format (USD, EUR, GBP…), either mapped from a column in your file or set as a static value for the whole dataset. Imports that ran fine without a currency started breaking overnight. It’s the dumbest and most common cause of imports that suddenly went dead this summer.
July 30, 2026, GBRAID / gad_ diagnostic. Google added an alert that flags properties where the gbraid and gad_ parameters vanish from click URLs, which reclassifies paid sessions as organic or (not set). That’s not the import itself, but it’s the same family of problem: the money and the session fail to find each other. A mislabeled session is a cost row with no one to attach to.
August 10, 2026, the validation report. This is the real news, the one that changes everything for diagnosis. Google finally provides a dedicated report (Reports > Data Import) showing join status and coverage rate per campaign. Before, you guessed. Now, you measure.
Reading the validation report without getting it wrong
The report fits in five dimensions (Source/Medium by default, plus Campaign ID, Campaign name, Source platform, and Default channel group) and seven metrics (import join status, coverage rate, event count, ad clicks, ad cost, ad impressions, key events). The central column, the one you need to learn to read, is the import join status. Every row lands in one of three states, and the nuance between the two bad states is critical:
| Join status | What it means | What you should do |
|---|---|---|
| Joined | The imported spend attached to measured sessions. All good. | Nothing, other than watch the coverage rate. |
| No campaign data | GA4 has sessions for this source/medium, but no imported spend against them. | Check your cost file: forgotten campaign, missing date, incomplete mapping. |
| No Analytics data | The spend exists in your import, but no session matches it in GA4. | The nastiest one: your UTMs don’t match, or the traffic was never labeled correctly. |
The classic trap is treating No campaign data and No Analytics data as the same bug. They’re opposites. In the first case, you’re missing spend (the file is incomplete). In the second, you’re missing sessions: the spend is there but it keeps polluting the denominator of your ROAS, since you have cost with no matching revenue or conversion. That’s exactly the kind of thing that makes you conclude “Meta was terrible this month” when Meta converts very well, but your Meta sessions are labeled facebook / paid on one side and Facebook / cpc on the other.
match rate vs percentage imported vs coverage rate
Here’s the distinction nobody takes the time to explain, and that’s a shame because it’s the key to the whole diagnosis. Three indicators float around, they measure three different things, with three different denominators. Confuse them and you’ll fix the wrong problem.
| Indicator | What it measures | Denominator | What a low number reveals |
|---|---|---|---|
| Percentage imported | Share of your file’s rows accepted at upload | Rows in the imported file | A format problem: dates, currency, mismapped columns |
| Match rate | Share of imported rows that find a matching key | Valid imported rows | A taxonomy mismatch: your UTMs don’t line up |
| Coverage rate | Share of your paid traffic actually covered by spend | Measured paid sessions | Whole swaths of traffic with no cost attached |
The textbook case to keep in mind: match rate at 100%, coverage rate at 30%. Translation: everything you uploaded matched (your file UTMs are clean), but you only uploaded a third of your platforms. Match rate reassures you for the wrong reason; it’s the coverage rate that tells the truth about how reliable your overall ROAS is. A perfect match rate on an import that only covers one platform out of three is worthless for allocating a budget.
A gray area worth naming, because Google doesn’t settle it: the documentation doesn’t explicitly say whether the coverage rate inherits the same rolling two-year window that constrains match rate, nor what threshold counts as “acceptable”. Until Google clarifies, treat the coverage rate as a relative indicator to watch over time, not an absolute grade out of 100.
Why joins fail: the 6 real causes
In practice, a No Analytics data or a floor-level match rate almost always comes down to one of these six causes:
- UTM casing. GA4 is case-sensitive.
Facebookis notfacebook,CPCis notcpc. One stray capital and the row won’t join. - Naming drift.
SpringSalein your cost file,Spring_Salein your campaign URLs. Two humans, two conventions, zero join. - Date format. Outside ISO 8601, or worse, a date in the future (mishandled time zone). GA4 has nothing to attach those costs to.
- Missing currency. Since July 28, 2026, this is a dealbreaker. No currency, no import.
- Multi-value taxonomy on one platform. The great classic:
fb / cpc,meta / cpcandfacebook / paidcoexist to mean Meta. Your Meta spend is split across three source/mediums, and none of them totals the real budget. - Unresolved dynamic UTMs.
{{campaign.name}}or macros left unresolved at mapping time, which land in sessions as unusable literal strings.
None of these causes is exotic. They’re all taxonomy hygiene issues, and that’s good news: they’re fixable. If you audit your setup regularly, add campaign import to your routine (see our GA4 audit of the 11 configuration mistakes; unassigned traffic there is a direct cousin of these join problems).
Fixing your UTM taxonomy
The fix comes down to one principle: one platform, one source, one medium, always the same. Three patterns exist, and only one is truly comfortable.
The best is one source per platform: meta / cpc, tiktok / cpc, pinterest / cpc. Each platform has a unique, stable identity, the automatic integration works, and the matching is trivial. That’s the best practice, full stop.
Acceptable: one source per family, when you deliberately group variants under a banner (for example all paid social under paidsocial). You lose per-platform detail, but at least the join holds.
To avoid: multi-value taxonomy, where the same platform appears under several source/mediums. There, automatic integration becomes impossible and you’re stuck doing a manual CSV import, Sheets, or BigQuery to stitch the pieces back together yourself. On the import-source side, GA4 offers eleven, including five direct integrations that spare you the manual file: Meta, Pinterest, Reddit, Snap and TikTok. If you’re on those platforms, wire up the direct integration rather than maintaining a CSV by hand. To consolidate residual source variants, the GA4 Source Group dimension is a useful complement on the reporting side.
Verify and track over time
One last reflex, and not a minor one. The validation report applies a default filter on paid manual campaigns, which excludes google / search (auto-tagged). That’s consistent, since the import concerns non-Google, but know it: if you want a complete view, remove the filter. Also expect a delay of about 24 hours before import data is reflected in the report; don’t panic if this morning’s upload isn’t showing up yet.
And above all, watch the coverage rate before you run a Scenario Planner. This is the most underestimated point: cross-channel budgeting builds its projections on this cost data, and a low coverage rate silently contaminates the projections without ever raising an alert. You get reallocation recommendations that look serious, computed on a third of reality. If the join stays unmanageable anyway, there’s a plan B: compute your non-Google ROAS outside GA4, combining costs and sessions in BigQuery, where you keep control of the join key.
In short: open the validation report, look at coverage rate first (not match rate), hunt the No Analytics data rows inflating your ROAS denominator, and fix your UTMs toward a single source per platform. Do that once, cleanly, and you’ll stop making budget decisions on numbers that lie.