GA4 Campaign Data Import Validation Report: Finding the Cost That Never Joined
Non-Google ad spend reaches GA4 through campaign data import. You upload cost, clicks and impressions from Meta, LinkedIn, TikTok, or an agency spreadsheet, and Analytics joins those numbers to the sessions it already recorded. When the join works, you get cost per acquisition and return on ad spend for the channels Google Ads cannot see.
The join fails more often than teams realise, and until recently nothing told you. A file could import cleanly, report every row as accepted, and still attach to nothing, because the values inside it did not line up with the values your site logged. Cost would appear in one report, no conversions would sit against it, and no error would surface anywhere.
On 10 August 2026 Google added the missing feedback loop: a campaign data import validation report that shows, per campaign and per dimension, whether your imported rows actually joined to user activity in the property.
What the August 10 release added
Google's release note is brief. It describes a new validation report to help you verify and improve the quality of your campaign data imports, one that helps you assess non-Google and imported campaign data, pinpoint campaigns that lack useful performance data such as cost, clicks and impressions, and review data you imported earlier.
The Help Centre entry behind it is more specific. It calls the report an enhanced report, session scoped, that lets you access imported campaign data using match rate calculations. Two of those details change how you read it. The report counts sessions rather than events, and it opens with a filter already applied.
Where the report lives, and what it hides by default
Open Reports, then Data import, then Campaign data import validation report. If it is not in your left navigation, an Editor or above can add it back, because report collections are customisable per property.
The default filter is the part that trips people up. The report applies a filter that keeps only paid manual campaigns and excludes all Google sources and search mediums. That is deliberate, since it isolates the traffic whose cost depends on your import work rather than on a native integration. Google documents the filter as removable, and removing it widens the report to all traffic in the property. For validation work, leave it on. Take it off only when you want the wider picture.
The three import join statuses
Every row carries one of three states, and each one points at a different fix.
- Joined. The campaign data was successfully joined to Analytics data. This is what you want to see.
- No campaign data. The campaign data was not imported for that dimension, or it was not joined correctly. Either your file never carried that key, or it carried it in a form Analytics does not recognise.
- No Analytics data. The imported data was not joined and is reported separately without Analytics data. Cost, clicks and impressions still appear, because Analytics reports imported metrics on any combination of the imported required dimensions whether or not a join happened. The row simply has no session attached to it.
That third state is the one worth understanding. A report full of cost and empty of conversions is not a broken import. It is a successful import that never joined. The metric behind the status is the presence of a positive Ads clicks, Ads cost or Ads impressions value, from your linked integrations or from your upload.
Three numbers that answer three different questions
Once you start validating, you meet three percentages, and it is easy to treat them as one number. They are not.
Percentage imported sits on the data import details page. It divides rows successfully imported by rows in the file. A figure below 100 percent means your source file dropped rows, usually a mapping error or a malformed column. This measures the upload, not the join.
Match rate sits on the same page. It is the percentage of imported rows that joined with Analytics data, joined on the required keys of utm_source, utm_medium and date. Google states that the calculation considers the most recent two years of imported data only, so a three-year backfill reports its match rate across the recent two years alone.
Coverage rate sits in the validation report. It divides successfully joined rows by rows with collected Analytics data. Where match rate measures your import against itself, coverage rate measures how much of your live campaign traffic carries imported performance data. A low coverage rate with a high match rate usually means campaigns are running without UTMs that match the import.
Read together, the three locate the fault. Low percentage imported is a file problem. Low match rate is a key problem. Low coverage rate is a naming or tagging problem across the campaigns themselves.
The join keys and the exact-match rule
Three values decide whether a row joins: source, medium and date. Google's documentation is blunt about the requirement. The values you use for utm_source, utm_medium, utm_campaign and date in your imported file must exactly match the values your users logged. Its own example is that Facebook and facebook are not exact matches.
The UTM guide adds the required shape of the destination URL. Campaign links need utm_id, utm_source, utm_medium and utm_campaign. In the property, source and medium are required values, while campaign ID and campaign name are optional in the URL but useful in the import, because they give you something to group by.
Two consequences follow. Dynamic parameters are not resolved by the import interface, so a value such as {{campaign.name}} has to be resolved at the ad platform before you export. And a change in campaign naming, SpringSale against Spring_Sale, splits one effort into two sets of rows, only one of which will carry the sessions your site recorded.
Keep one thing separate. The Source Group dimension consolidates platform spellings into a single reporting value, which cleans up reports. The import join runs on the raw utm_source, utm_medium and date values, so treat reporting consolidation as a different layer from what makes a row join.
Why joins fail in practice
Four causes account for most of the damage, and none of them requires a new tool to fix.
Spelling drift. One platform sends fb, another sends facebook, a third sends Meta-facebook, and the import file inherits whichever value the export produced. Fix the naming at the source and re-export, because editing the file to match is a patch the next upload undoes.
A missing medium. Links that carry only a source parameter leave the session with a default medium. The imported row claims cpc. Nothing matches.
URL encoding and whitespace. A value arriving as ad+network, or carrying a trailing space in a CSV column, is a different string from ad network. Spreadsheet exports are the usual source of invisible whitespace, so trim the columns before upload.
Date boundaries. The join includes the date, and imported metrics are aggregated daily. If your ad platform reports on a different time zone boundary than the property, a day of spend lands on a date with no matching sessions. Check the time zone on both sides before you blame the file.
One timing point is worth holding onto. Google notes that uploaded data can take up to 24 hours to appear in reports, audiences and explorations, and that users have to engage with a campaign after you upload for the metrics and campaign properties to be associated with their activity. A campaign you switched on this morning and imported this afternoon will not show a clean join yet.
A triage routine
Work through this once per property when a client questions their non-Google ROAS.
- Open the validation report and confirm the default filter is on, so you are looking at paid manual traffic.
- Sort by coverage rate. The campaigns at the bottom of that sort are the ones whose cost sits in the property without sessions attached.
- For each weak campaign, read the join status. No campaign data means the import never carried the key. No Analytics data means the key is present but matches nothing the site logged.
- Compare the imported source and medium strings against the ones in your own reports for the same period. This is where fb against facebook becomes obvious.
- Check the uploaded date range against the platform's time zone.
- Fix at the source, meaning the UTM generator or the export, then re-upload and wait a day before judging the result.
- Compare total cost, clicks and impressions against the third-party platform for the same window. That cross-check catches what the join status cannot.
What the report cannot tell you
The report shows join status and coverage, and it does not diagnose the cause. It will not tell you which string failed to match, and it will not tell you whether a row joined to the wrong campaign, because a mismatch that lands on a different campaign still reads as joined. The comparison against the ad platform remains the only way to catch that, which is why that step stays in the routine.
Two limits from the underlying import also apply. Match rate considers the most recent two years of data only. And deleting an import source removes its data from reports without touching the events already collected, because campaign data import joins at query time rather than rewriting processed data.
The UK angle
Imported cost is not collected from a visitor, so consent does not gate the upload itself. It does gate the sessions the row has to join. A property running a default denied state collects a narrower set of identified sessions, and a narrower set of joinable sessions shows up in the coverage rate. That is a measurement reality rather than a bug, and it is why a UK property and a US property with identical import setups can report different coverage.
The ICO guidance on storage and access technologies, finalised in April 2026, sets the frame. Analytics used to improve your own service can sit inside the statistical purposes exception, provided you give clear information and a simple means of objecting free of charge. Advertising measurement stays behind consent. When you validate an import you are checking the joinability of data collected under that regime, so the coverage rate is also a rough read on how much consented session data the property has to work with.
There is an accountability angle too. If a client challenges reported ROAS, the validation report is dated evidence that the join was tested rather than assumed. That is cheaper than reconstructing the position six months later.
Campaign data import has always been a join between two systems that describe the same campaign differently. The validation report is the first time GA4 has shown you which side of that join failed. Run it before you trust a non-Google ROAS figure in a client deck.
Is Your Imported Cost Joining?
North Digital reads the validation report against your live campaign traffic, traces the rows that never joined, and fixes the naming at the source so the cost lines up with the sessions. You get a clean non-Google ROAS and the evidence to defend it.
Book a Tracking Review