GA4 Campaign Data Import 2026: Schema, Join Keys and a ROAS You Can Trust
As long as spend lives in five ad platforms and conversions live in GA4, every ROAS conversation turns into an argument about whose spreadsheet is right. GA4 campaign data import closes that gap: you feed GA4 the daily cost, clicks and impressions of your non-Google channels, and the reports start calculating cost per click and return on ad spend for every source inside one system. In 2026 Google renamed the feature — what used to be cost data import is now campaign data import. Below: the exact file schema, the join keys, the limits, the UTM traps that quietly break everything, and an honest list of what this report will never tell you.
What GA4 campaign data import actually does
GA4 knows how many sessions and conversions arrived from utm_source=bing. It has no idea what you paid for them. The import supplies the missing half: for each source/medium pair on a given day you submit cost, clicks and impressions, and GA4 joins that to its own behavioural and revenue data.
The output is non-Google cost per click and return on non-Google ad spend inside standard reports — no exports, no manual reconciliation in a sheet.
You do not import Google Ads. Its spend flows in automatically through the account link. This feature is for everything else: paid social, native networks, push and teaser traffic, paid newsletter placements, flat-fee partnerships.
The schema: required and optional fields
Dimensions
| Field | Status | Notes |
|---|---|---|
Campaign source (utm_source) |
required | join key |
Campaign medium (utm_medium) |
required | join key |
| Date | required | ISO 8601, YYYY-MM-DD |
Campaign ID (utm_id) |
optional, highly recommended | more stable than a campaign name |
Campaign name (utm_campaign) |
optional, highly recommended | makes reports readable |
| Currency | required if cost is included | ISO 4217 code |
Metrics
At least one of three is required: Clicks, Cost, Impressions — all as daily values. Cost is submitted in the 0,000.00 format.
CSV headers
The template the uploader expects:
utm_id, utm_campaign, utm_source, utm_medium, date, impressions, clicks, cost
A sample row: bing_123abc | Summer_fun | bing | cpc | 2026-12-01 | 12242371 | 367271 | 36727.10.
Date constraints: later than 2013, never in the future. You can either map currency from a field in your file or hardcode it as a static value during mapping, in which case it applies to every cost value in that import.
Join keys: where this breaks
GA4 joins imported data to its own data on three fields: utm_source, utm_medium and date. The match must be exact — the documentation spells it out with the example that “Facebook” and “facebook” are not exact matches.
Two rules follow:
- Your links and your file must carry identical values. If traffic is tagged
fb/cpcand you uploadfacebook/cpc, the spend lands as an orphan row joined to nothing. - Case and separators are part of the key.
Metaandmeta,paid_socialandpaid-socialare different sources.
utm_term and utm_content cannot be included in the file at all — they are fine in URLs, but the import rejects them. Inconsistent campaign naming (SpringSale vs Spring_Sale) splits one effort into two report rows. We cover building one tagging standard across the ad platform, GA4 and your CRM in UTM tagging and naming conventions.
Overwrites and re-uploads
Uploading a file with the same set of keys overwrites — it does not add. That matters: ad platforms restate yesterday’s spend after the fact (adjustments, refunds, filtered traffic), so re-uploading the last few days every day is normal practice. Reporting always uses the most recently uploaded values.
A single file cannot contain duplicate keys — each row must be unique on source + medium + date.
Where imported data shows up
- Reports → Acquisition → Non Google campaign — the primary view.
- Advertising → Planning → All channels — the cross-channel roll-up.
- Reports → Data import → Campaign data import validation — the reconciliation report.
One structural detail drives everything else: the join happens at query time, not at collection. Three consequences:
- It works against already-processed events, so you can backfill historical spend and past periods will recalculate.
- Delete the import source and the data disappears from reports — nothing is baked into historical tables.
- Query-time data is not available when building audiences or segments in Explorations. You cannot create an audience of “users from channels where CPC exceeded X.”
Data takes up to 24 hours to appear after upload. Do not debug in the first hour.
Two health metrics worth watching
| Metric | Meaning |
|---|---|
| % imported | share of file rows accepted. 100% means nothing was dropped |
| Match rate | share of imported rows successfully joined to GA4 data. Computed over the most recent two years of imported data only |
A subtlety worth internalising: cost, clicks and impressions are reported against any combination of the imported dimensions regardless of match rate. Even at zero matching you will see spend — it simply will not connect to user behaviour. A low match rate almost always means tagging mismatch rather than an upload failure.
Ways to deliver the data
Manual CSV upload
Fine for getting started and for low-frequency channels. The drawback is obvious: someone has to do it daily.
Scheduled SFTP
You provide a server username, a server URL, a frequency (daily, weekly, monthly) and the hour the upload starts. The server must support the ssh-rsa or ssh-dss host-key algorithms; the default port is 22. GA4 issues a public key you authorise on your side. Daily is the finest granularity available.
Warehouse and storage connectors
In open beta, and only for campaign data: Amazon Redshift, Amazon S3, BigQuery, Google Cloud Storage, Google Sheets, HTTPS, MySQL, PostgreSQL, SFTP and Snowflake. If you already maintain a spend table in a warehouse, this is the cheapest path to maintain. We explain how GA4’s own event export works and what you can compute on it in GA4 BigQuery Export for paid traffic.
Direct ad-platform connectors
GA4 supports Meta, Pinterest, Reddit, Snap and TikTok directly. They run automatically every day, with two caveats:
- on connection the source attempts to pull 24 months of historical data into the property;
- all paid traffic from that platform must use a single unique
utm_sourceand a single uniqueutm_medium. If you are runningfb/cpc,Meta/cpcandfacebook/paidsimultaneously, the connector will not work — use CSV, Sheets or BigQuery instead.
And a warning: if overlapping campaign data already exists from another import source, delete those datasets first or you will double-count spend.
Limits to know before you design the pipeline
| Limit | Value |
|---|---|
| Size per import source | 1 GB |
| Uploads per property per day | 120 |
| Campaign data storage | 1 GB across all import sources |
| Time until data appears | up to 24 hours |
| Best SFTP frequency | once daily |
Current usage is visible through the quota information button in the interface. One more detail for larger setups: subproperties and roll-up properties do not inherit query-time widening imports, so campaign data has to be configured in each property separately.
Setup, step by step
- You need Editor or above at property level.
- Admin → Data collection and modification → Data import.
- Create import source, give it a name.
- Select the data type — campaign data.
- Pick the import source: manual CSV upload, SFTP, or a connector from the dropdown.
- Next → map your file’s fields to GA4 fields. You set currency here, either from a field or as a static value.
- Import.
A detail that trips people up at mapping: dynamic parameters such as {{campaign.name}} are not accepted. They are encouraged in the ad platform’s own URL settings, but they must be resolved to literal values before the file reaches GA4.
To add data to an existing source, use the import button on its row. The file must contain the same fields or a subset of them; changing the field set means deleting the source and creating a new one.
What this report will not do
- It is not attribution. The import adds money to an existing model; it does not change how GA4 distributes conversions across channels.
- It is not reconciliation with the ad platform. You supply the spend. A bad export produces a confident, wrong report.
- It is not audience data. Query-time data cannot be used for segments in Explorations.
- It does not fix conversion discrepancies. If GA4 and your ad platform count conversions differently, importing cost will not help; the causes and a diagnostic order sit in Google Ads and GA4 conversion discrepancy.
- It does not replace measurement on site. If half your conversions are lost in the browser, accurate spend simply gives you an accurate picture of a loss; fix the floor first with Google tag gateway.
A worked example: what the report looks like
Say you upload a week of spend for three third-party channels while GA4 counts conversions and revenue on its own. The non-Google campaign report starts reading like this:
| Source / medium | Cost | Clicks | Conversions | Revenue | CPC | ROAS |
|---|---|---|---|---|---|---|
| meta / cpc | 4,200 | 5,600 | 128 | 11,900 | 0.75 | 2.83 |
| tiktok / cpc | 2,100 | 4,900 | 41 | 3,150 | 0.43 | 1.50 |
| partner_mail / email | 900 | 1,150 | 37 | 4,100 | 0.78 | 4.56 |
The numbers are illustrative; the reading pattern is the point. The cheapest click belongs to the worst-returning channel — invisible until the spend sits in one system, because inside TikTok’s own dashboard that campaign looks healthy on CPC and CTR. Second, the smallest budget shows the best ROAS, which usually means “this channel is capped by audience size,” not “move the entire budget here.” Third, the three rows are only comparable because one tool counted conversions and revenue under one set of rules.
Decide explicitly what counts as revenue. If GA4 receives order value at checkout, ROAS runs optimistic: refunds, cancellations and unpaid orders never reach that figure. An honest picture needs either confirmed revenue pushed from the CRM or a correction factor stated openly in the report.
Common mistakes in a GA4 campaign data import file
- Decimal separator. Submitting
36727,10instead of36727.10is the classic reason rows import fine and totals look absurd. - Implicit currency. If the ad platform bills in one currency and the GA4 property reports in another, and the file says nothing, the math drifts. Set the ISO code explicitly or hardcode it at mapping.
- Tax handled inconsistently. Decide once, for every source, whether cost includes tax — otherwise cross-channel comparison is meaningless.
- Assuming rows add up. A daily job re-uploading “yesterday” is safe because identical keys overwrite. But change
utm_campaignwhile keeping the same source/medium pair and the campaign-level view will surprise you. - Platform-local dates. Ad platforms close the day in their own time zone; GA4 reports in the property’s. Invisible weekly, obvious daily.
- Importing Google Ads anyway. That double-counts against the automatic account link.
A one-week rollout
- Day 1. Export the actual
utm_source/utm_mediumvalues GA4 is receiving for paid traffic. That is your reality, not what the media plan says. - Day 2. Collapse tagging to one standard: one platform, one pair of values. Fix it in the ad platforms, not in old links.
- Day 3. Build a test CSV covering the last seven days for one channel. Check date format, currency and the decimal separator.
- Day 4. Upload, wait 24 hours, read % imported and match rate.
- Day 5. Work through mismatches in the validation report and correct the values.
- Days 6–7. Automate: scheduled SFTP or a warehouse connector, re-uploading the last 3–5 days daily.
Once every channel’s spend sits in one place, budget allocation becomes a conversation about numbers instead of instincts. For the Google side of the picture, keep the channel breakdown of automated campaigns next to it — see Performance Max channel reporting. And if your Q4 plan leans on discounting, the rules behind the Deal Ends annotation in Google Shopping are worth reading before you set the calendar.
Reconciling with the ad platform: what is normal
The first question after rollout is always why GA4 and the platform dashboard disagree. Here is where to expect a gap and where it signals a break.
| Comparison | Expected gap | When it is a problem |
|---|---|---|
| Daily spend | near zero — you supply it yourself | any visible difference means a broken export or a time-zone mismatch |
| Clicks vs. sessions | sessions always lower; some users leave before the page loads | a multiple-fold gap points at landing speed or a broken tag |
| Platform conversions vs. GA4 conversions | substantial, from different models and windows | the gap widening month over month means measurement is drifting |
| GA4 revenue vs. CRM revenue | GA4 usually higher | a difference larger than your refund and cancellation rate |
GA4 ships a validation report for exactly this: it shows which source/medium pairs are already arriving in the property and lets you compare imported totals against what the platform reports. Start debugging there rather than in the general reports — mismatches surface immediately and without interpretation.
Keep in mind that GA4 aggregates imported metrics daily. Hourly analysis or ad-level breakdowns are impossible on this data: the finest grain available is day, source, medium and optionally campaign.
Where PPC Rebels fits
Consolidated spend is only as useful as the campaigns behind it are stable. If your team owns the analytics layer but keeps getting stuck on launches and access, that half is worth delegating: agency Google Ads accounts and account verification take the infrastructure question off the table, while the Google Ads guide covers the structural and measurement basics.
FAQ
What is campaign data import in GA4?
A mechanism for uploading daily cost, clicks and impressions from non-Google advertising channels so GA4 can report cost per click and return on ad spend across all sources. Its previous name was cost data import.
Do I need to import Google Ads spend?
No. Google Ads data flows in automatically through the account link. The import exists for third-party channels.
Which fields are required?
utm_source, utm_medium and date, plus at least one metric — clicks, cost or impressions. If you include cost, you also need currency in ISO 4217.
How does GA4 join the data?
On three fields: utm_source, utm_medium and date. The match must be exact, including letter case.
Can I backfill historical spend?
Yes. The join happens at query time, so imported cost applies to events that were already processed.
What happens if I upload the same period twice?
Rows with the same keys overwrite each other; GA4 does not sum them. Re-uploading recent days is normal practice because ad platforms restate spend after the fact.
Why is my match rate low?
Almost always tagging mismatch: the values in your file differ from what actually arrives in the links. Remember case sensitivity — “Facebook” and “facebook” are different.
Can I build audiences from imported cost?
No. Query-time data is unavailable when creating audiences in Analytics or segments in Explorations.
What are the limits?
1 GB per import source, 1 GB total for campaign data, 120 uploads per property per day, and up to 24 hours before data appears in reports.
Which platforms have direct connectors?
Meta, Pinterest, Reddit, Snap and TikTok. They run automatically every day and pull up to 24 months of history on connection.
Why can’t I use a connector?
Direct connectors require all traffic from a platform to use one utm_source value and one utm_medium value. With multiple variants in play, use CSV, Google Sheets or BigQuery instead.
What happens if I delete the import source?
Campaign data disappears from reports, because the join is performed at query time and never written into processed data.