Everyone is talking about Meridian, Google’s open-source MMM, and yet every tutorial stops in the same place: the Bayesian model, a pip install, and a sample notebook running on data that is already clean. Nobody writes the one part that actually blocks practitioners: turning your event-level, daily GA4 BigQuery export into the weekly, per-geo, per-channel table that Meridian expects as input. That is where 90% of MMM projects stall, and that is exactly what this guide fixes, SQL query included.
If you are looking for a definition of marketing mix modeling, you are in the wrong place. Here we assume the decision has been made (by you or by your leadership), that the GA4-to-BigQuery export is already running, and that the real question is: which columns, which grain, and where the media costs come from. So we go straight to the table format and the SQL that produces it.
Why MMM is back (and why now)
MMM is not new, it was just shelved during the golden age of the cookie. It is coming back because user-level attribution is collapsing. The end of the Privacy Sandbox, the Digital Omnibus six-month rule, and the April 2026 restructuring of GA4 attribution (shorter lookback window, first-click removed) all share one trait: they break models that track a single user from a click to a conversion. MMM, by contrast, is aggregated and cookieless. It does not look at individuals, it looks at time series of spend and outcomes. That is precisely what makes it robust in a world that is losing its identifiers.
Google clearly sees this and is pushing hard on Meridian in 2026: a no-code Scenario Planner in the spring, then the announcement of Meridian GeoX (open-source geo-incrementality) and Meridian Studio on May 5, 2026, ahead of Google Marketing Live. Interest is rising, not yet saturated. For the full context on the attribution collapse, I covered the changes in GA4 Attribution Changes in 2026 and Privacy Sandbox Is Dead. We will not redo those articles here, we go to the concrete part.
What Meridian actually expects as input
First shock for most people: Meridian does not want your events. It wants an aggregated table, one row per combination of week and geographic area, with media spend in columns. Your GA4 export is the opposite: one row per event, timestamped to the microsecond, with no notion of media cost. All the prep work consists of getting from one to the other.
Meridian loads its data through a DataFrameDataLoader and a CoordToColumns object that maps your actual column names to the model’s standard coordinate names. Here are the columns that matter:
| Meridian column | Required | Role | Where it comes from |
|---|---|---|---|
time | Yes | The week, in yyyy-mm-dd format | GA4 BigQuery (event_date aggregated to the week) |
geo | Yes (at least 1) | The geographic unit (country, region, market) | GA4 (geo.country, geo.region) |
kpi | Yes | The metric to model (purchases, leads, revenue) | GA4 (count of purchase, or revenue) |
revenue_per_kpi | Yes | Average revenue per KPI unit | GA4 (purchase_revenue divided by purchase count) |
population | Yes | Population of each geo (normalization) | External source (Census, INSEE, etc.) |
media | Yes | Media exposure per channel (impressions, clicks) | Ad platforms or Ads Data Transfer |
media_spend | Yes | Spend per channel | Cost import, Ads Data Transfer, connectors |
controls | Recommended | Control variables (season, promo, price) | External + GA4 |
Three things to carve in stone. The time grain is weekly, not daily: Meridian behaves poorly on noisy daily data, and the week is the industry standard. The date format is strictly yyyy-mm-dd. And if you only have a single market, you are doing national MMM: a single geo value is allowed, but you lose the statistical power that the geographic dimension gives the model.
The BigQuery query: from GA4 events to weekly KPI
Here is the concrete bridge. This query takes the standard GA4 export (events_*), deduplicates purchases, aggregates into the Meridian format, and outputs a time / geo / kpi / revenue_per_kpi table directly. Adjust the dataset name and the date range.
WITH purchases AS (
SELECT
event_date,
event_timestamp,
geo.country AS geo,
(SELECT value.string_value
FROM UNNEST(event_params)
WHERE key = 'transaction_id') AS transaction_id,
ecommerce.purchase_revenue AS revenue
FROM `your_project.analytics_123456789.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20240101' AND '20261231'
AND event_name = 'purchase'
),
dedup AS (
SELECT * EXCEPT(rn) FROM (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY transaction_id
ORDER BY event_timestamp
) AS rn
FROM purchases
WHERE transaction_id IS NOT NULL
)
WHERE rn = 1
)
SELECT
DATE_TRUNC(PARSE_DATE('%Y%m%d', event_date), WEEK(MONDAY)) AS time,
geo,
COUNT(*) AS kpi,
SAFE_DIVIDE(SUM(revenue), COUNT(*)) AS revenue_per_kpi
FROM dedup
GROUP BY time, geo
ORDER BY time, geo
Three things are worth stopping on. The PARSE_DATE('%Y%m%d', event_date) converts the GA4 event_date string (formatted 20260831) into a real date, without which DATE_TRUNC refuses to work. The WEEK(MONDAY) anchors the week on Monday: pick a convention and hold it across the whole table, media costs included, otherwise your weekly joins will be off by a day and skew everything. Finally, deduplication by transaction_id avoids double-counting purchases fired more than once, a classic of e-commerce tracking. If you do not have a reliable transaction_id, deduplicate at least on event_timestamp per user.
To go deeper on the GA4 BigQuery export, Working with the GA4 BigQuery Export covers the table structure, and the 10 essential BigQuery queries gives you the base SQL toolkit.
Attaching media cost: the part everyone rushes
Your weekly KPI is only half the model. Without media_spend, Meridian has nothing to correlate. You have three sources for cost, and the choice is not trivial.
GA4 campaign data import is the simplest route if your non-Google costs (Meta, TikTok) are already uploaded. But beware: this import joins at query time on utm_source, utm_medium and date, and it breaks silently as soon as your UTM taxonomy drifts. I wrote a full guide on that trap in GA4 Campaign Data Import. A broken import means phantom spend inside your MMM.
Google Ads Data Transfer exports your Google Ads costs straight into BigQuery, at the source, without going through GA4. It is the cleanest route for the Google channel, because it avoids sampling and fragile joins. You then reconcile those costs with your KPI table by week and by geo.
Third-party connectors (Meta, TikTok, LinkedIn via an ETL, or the Data Manager) feed the rest. The Data Manager is in fact becoming the orchestration hub for these flows in 2026: see Google Ads Data Manager. My recommendation: Ads Data Transfer for Google, connectors or ETL for the rest, and align everything on the same Monday-to-Sunday weekly grain before joining.
The control variables everyone forgets
An MMM without control variables attributes to media variations that have nothing to do with it. The controls are there to absorb everything that moves your KPI without being advertising: seasonality (sales, holidays, back-to-school), the promotions and discounts you ran, price level, and, very useful for retail, Google Query Volume (the volume of searches on your brand, a proxy for organic demand). Skip them, and the model will overestimate the ROI of your paid channels by crediting them with the December bump. Add them, and your response curves finally become credible.
The 3 traps that silently invalidate your MMM
These three mistakes do not crash Meridian. They produce a model that runs, spits out numbers, and is wrong. That is the worst case, because you make decisions on it.
The first trap is the already-attributed KPI. If your kpi comes from a GA4 report based on attribution (data-driven attributed conversions, for example), you are mixing two incompatible logics: media is already credited on the KPI side, then Meridian tries to re-credit it on the media side. Guaranteed double-counting. An MMM’s KPI must be a raw, unattributed total: the number of purchases, full stop. It is precisely because attribution is becoming unreliable that you move to MMM, so do not reinject attribution into the input.
The second is the lack of geo granularity. GA4 does expose geo.country and geo.region, but many implementations only have one usable country, or regional data that is too sparse. Without several geos, you lose the power of Meridian’s hierarchical model and fall back to a weaker national MMM. Check the coverage of geo.region before promising a geo model.
The third is the history that is too short. Below two years of weekly data, the model has not seen enough seasonal cycles or enough spend variation to be identifiable. It will produce credible intervals so wide they mean nothing. Two years is a practical floor, three years is comfortable. If you only have one year of BigQuery export, wait, or backfill with an older cost history.
Meridian Scenario Planner is not the GA4 Scenario Planner
Watch out for a naming trap that confuses everyone. Meridian has a Scenario Planner, released as no-code in spring 2026, which simulates budget allocations from the MMM’s response curves. The GA4 Scenario Planner is a different thing: a cross-channel budgeting feature built into the GA4 interface, based on its own projections, not on a Bayesian MMM. Same name, two tools, two methods, two levels of rigor. Do not conflate them in a client recommendation. I covered the latter in detail in GA4 Cross-Channel Budgeting: Scenario Planner and Limits. If a decision-maker mentions Scenario Planner, your first question should be: which of the two.
When NOT to build an MMM
Let’s end with advice that cuts against all the hype: MMM is not for everyone. Below roughly €50k per month in media spend, or with a single active channel, you have neither the budget nor the statistical variation needed for the model to learn anything. You will spend weeks preparing data only to get gigantic credible intervals. In that case, solid UTM discipline, clean server-side tracking, and isolated geo-test incrementality analysis will serve you better than a full MMM.
If, on the other hand, you tick the boxes (several channels, a meaningful budget, two years of history, several geos), then data preparation is your real job, not the model. The model, Meridian does for you. The clean weekly table, by geo and by channel, is on you. Start with the query above, validate the grain and the deduplication, attach the costs, and you will have done 80% of the work before writing a single line of Python.