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;