You want a single Looker Studio dashboard showing Google Ads spend alongside GA4 conversions — so you can calculate ROAS without exporting data to spreadsheets. Looker Studio’s data blending feature can join these two sources, but the join key matters enormously. A wrong join key produces double-counted metrics, missing rows, or completely wrong ROAS calculations. This guide explains the correct approach to blending GA4 and Google Ads data.
The Core Challenge: Different Granularity
GA4 and Google Ads report at different granularities by default. GA4 has sessions with source/medium. Google Ads has campaigns, ad groups, and keywords. The join key must be a dimension that exists in both sources at the same level of detail.
- Date: Works for daily totals but loses campaign-level detail
- Date + Campaign name: Best for campaign-level ROAS
- Date + Source/Medium: Works if GA4 source/medium matches Google Ads campaign naming
Setting Up the Blend
- In Looker Studio: Add chart → Data → Blend Data
- Source 1 (Left): Google Ads connector (or BigQuery Google Ads export)
- Source 2 (Right): GA4 connector (or BigQuery GA4 export)
- Join key: Date (at minimum), add Campaign if available in both
- Join type: Left join (keep all Google Ads rows even if no GA4 match)
-- If using BigQuery for both, join in SQL for more control:
SELECT
ads.date,
ads.campaign_name,
ads.cost,
ads.clicks,
ads.impressions,
ga4.sessions,
ga4.conversions,
ga4.revenue,
SAFE_DIVIDE(ga4.revenue, ads.cost) AS roas,
SAFE_DIVIDE(ads.cost, ga4.conversions) AS cost_per_conversion
FROM `project.dataset.google_ads_campaign_stats` ads
LEFT JOIN (
SELECT
event_date as date,
-- Extract campaign from UTM parameters
(SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'campaign') as campaign_name,
COUNT(DISTINCT CONCAT(user_pseudo_id, '_',
CAST((SELECT value.int_value FROM UNNEST(event_params) WHERE key = 'ga_session_id') AS STRING)
)) as sessions,
COUNTIF(event_name = 'purchase') as conversions,
SUM(CASE WHEN event_name = 'purchase'
THEN (SELECT value.float_value FROM UNNEST(event_params) WHERE key = 'value')
ELSE 0 END) as revenue
FROM `project.dataset.events_*`
WHERE _TABLE_SUFFIX >= FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL 90 DAYS))
AND traffic_source.medium IN ('cpc', 'ppc', 'paid')
GROUP BY date, campaign_name
) ga4 ON ads.date = ga4.date AND ads.campaign_name = ga4.campaign_name
WHERE ads.date >= DATE_SUB(CURRENT_DATE(), INTERVAL 90 DAYS)
ORDER BY ads.date DESC, ads.cost DESC;