How to Track A/B Test Events in GA4 (Step by Step)
Track A/B test events in GA4: the impression event, custom dimensions, the Exploration report and the timing mistakes that silently corrupt the data.

📚 This article is part of the guide GA4 and A/B Testing: The Complete Integration Guide.
GA4 does not know what an A/B test is. It has no experiment object, no variation dimension and no built in significance calculation, so tracking a test there is entirely a design job: you decide which event proves exposure, which parameters carry the experiment and variation identifiers, and which report puts them side by side. This guide is a child of the complete guide to GA4 and A/B testing and covers only the implementation path, from the first dataLayer push to a report you can trust enough to paste into a significance calculator.
The short version: send one custom event when a variation is rendered, carry two parameters on it, register those two parameters as event-scoped custom dimensions, wait for the dimensions to start populating, and build an Exploration that groups your conversion by the variation dimension. Everything else in this article is about the ways that sequence goes wrong.
Why tracking an A/B test in GA4 needs a structure of its own
Google Optimize was retired on 30 September 2023, and with it went the native integration that used to inject experiment and variant dimensions into Analytics for free. Since then, every team running tests alongside GA4 has been rebuilding the same plumbing by hand, usually badly, because the default instinct is to tag the conversion instead of the exposure.
Tagging the conversion is the original sin here. If you only mark conversions with the variation name, you have a numerator with no denominator: you know 410 people bought after seeing variation B, but you have no idea how many saw it at all, so you cannot compute a rate, and without a rate there is no test. The unit of an A/B test is the exposed visitor, so exposure is what has to be tracked first.
| What you tag | What you can compute | Verdict |
|---|---|---|
| Only the conversion | Absolute conversions per variation | Useless: no denominator, so no rate and no test |
| Only page views by URL | A rate, if the variations live on different URLs | Only works for split URL tests, breaks for same page tests |
| The exposure, plus the conversion | Rate, lift, confidence interval, p-value | The only structure that supports a real reading |
The recommended structure: one variation impression event
Send a single custom event, fired once per visitor per experiment, at the moment the variation becomes visible. Two parameters travel with it: the experiment identifier and the variation identifier.
// Fire this from the same code path that finishes applying the variation,
// not from a generic page load handler.
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'experiment_impression',
experiment_name: 'hero_cta_test',
variation_name: 'B'
});
Three properties of this design matter more than the naming:
- It fires after rendering. The push lives at the end of the function that applies the variation, so an event only exists if a human could have seen the change.
- It fires once per visitor per experiment. Firing on every page view multiplies the denominator by the number of pages a visitor browses, which mixes visitors with page views and makes the rate meaningless. Guard the push with a flag in
sessionStorageor with a variable that the snippet sets on first assignment. - It carries the experiment name, not just the variation. Two concurrent experiments both reporting variation “B” are indistinguishable without it.
Registering the two custom dimensions
Parameters that are not registered as custom dimensions are collected but not reportable, which is the single most common reason a correctly implemented event produces an empty report. In the GA4 interface the path is Admin, then Custom definitions, then Create custom dimension, twice:
| Dimension name | Scope | Event parameter | What it answers |
|---|---|---|---|
| Experiment name | Event | experiment_name |
Which test this exposure belongs to |
| Variation name | Event | variation_name |
Which side of the test the visitor saw |
Two operational details that cost teams a day each when they are missed. First, custom dimensions are not retroactive: they populate only for events collected after registration, so data from before that moment stays invisible in reports forever. Register the dimensions before you start the test, not after the first curious look at the numbers. Second, Google documents a limit of 50 event-scoped custom dimensions on standard properties, which is why the experiment identifier belongs in a parameter value rather than in a dimension per experiment.
Building the Exploration report
The default reports in GA4 will not show custom dimensions in a useful shape, so the reading happens in Explore, Free form:
- Import both custom dimensions and the metrics you need: total users, plus the conversion event count or key event count.
- Put Variation name in rows.
- Add a filter where Experiment name exactly matches the identifier of the running test, so concurrent experiments do not contaminate each other.
- Set the date range to the exact window the test ran, with no partial days at either end.
- Add the conversion as a column, so each row shows exposed users and conversions side by side.
Sending the event: tag manager or gtag directly
There are two ways to get that push into Analytics, and the choice has consequences beyond convenience.
With Google Tag Manager, the dataLayer.push above is picked up by a custom event trigger, two data layer variables read experiment_name and variation_name, and a GA4 event tag sends them. The advantage is that marketing can change the tag without a deploy, and the Preview mode lets you watch the event fire in real time before publishing the container. The cost is one more system that can be misconfigured, and a container publish step that people forget. The full tag manager setup guide walks through the trigger, the variables and the Preview check.
With gtag directly, you skip the container and call the Analytics library from the same function that applied the variation. The advantage is that the event cannot fire before the render, because it is the same line of code; there is no timing gap to introduce. The cost is that every change is a deploy, and the parameter names live in application code where marketing cannot see them.
| Criterion | Tag manager | gtag directly |
|---|---|---|
| Changing parameters without a deploy | Yes | No |
| Risk of firing before the variation renders | Higher, the trigger listens to an event you must place correctly | Lower, the call sits inside the render path |
| Debuggability before going live | Strong, Preview mode replays every fire | Weaker, usually the network tab and the DebugView |
| Ownership | Marketing or analytics | Engineering |
Neither is wrong. What is wrong is mixing them, sending the same impression from both paths and doubling the denominator on one side of the test.
The sampling limit on high-volume properties
Explorations are not always exact. Google documents that standard properties apply sampling to Exploration reports when the query covers more than roughly 10 million events for the selected date range, and marks the report with a sampling indicator. A sampled figure is an estimate with an error band of its own, and feeding an estimate into a significance calculation compounds two uncertainties into a number that looks precise and is not.
Three ways out, in order of preference: shorten the date range until the indicator disappears; narrow the segment so the query touches fewer events; or use the BigQuery export link and count the raw events with SQL, which removes sampling entirely. The BigQuery daily export on the free tier is documented at a limit of 1 million events per day, so very large properties need the paid path.
Why the GA4 numbers will not match your testing tool
They will differ, and expecting them to match to the visitor is the fastest way to lose a week chasing a bug that is not there. Four structural reasons, none of which is a defect:
- Consent. Under consent mode, Analytics tags are held or send cookieless pings until the visitor accepts, while a testing snippet that runs before the banner has already assigned and rendered a variation. The assignment exists; the Analytics record of it may not.
- Blocking. The Analytics collection endpoint is on essentially every blocklist. A first-party snippet served from your own domain is blocked far less often, so the tool sees visitors that GA4 never hears about.
- Counting unit. GA4 reports users and sessions on its own identity logic, including modelled behaviour in some reports. A testing tool counts assigned visitors on its own storage key. Two different denominators, by construction.
- Attribution window. A conversion that happens three days later is attributed by GA4 through its own lookback rules, while the testing tool ties it to the assignment that is still stored in the browser.
The practical rule: use GA4 to understand what happened around the experiment, segment by device, channel and geography, and inspect the downstream funnel. Read the verdict where the assignment lives. If the two disagree in direction rather than in magnitude, that is worth investigating; a gap of a few percent in absolute counts is not.
Timing and counting mistakes that corrupt the reading
- Firing the impression before the variation renders. The most damaging mistake, because it is invisible in the report. The variation branch does more work, so it loses more visitors inside the gap, so its denominator inflates and its measured rate drops. A test that was neutral reads as a loss.
- Firing on every page view. Turns exposed users into page views and inflates both denominators unevenly, since visitors do not browse the same number of pages on both sides.
- Using a user-scoped dimension. The last value wins, so a visitor exposed to two experiments carries only the second, and their earlier conversions are attributed to a variation they were never in.
- Starting the analysis window before the dimensions were registered. Produces a partly empty table that looks like a tracking bug and is actually a chronology bug.
- Comparing a full day against a partial day. Weekday and hour of day effects are large enough to swing a small test. Cut the range on whole days for both sides.
- Reading the table daily and stopping at the first green number. This is the peeking problem, and it is a statistics failure rather than a tracking failure. The common A/B testing mistakes guide quantifies how much the false positive rate rises when you look repeatedly.
A worked example, with the numbers exported from the report
Suppose you built the Exploration exactly as described and exported these rows, grouped by the variation dimension: control (A) had 9,000 users with the impression event and 360 conversions; variation B had 9,000 users and 410 conversions.
- Rate of A: 360 divided by 9,000 equals 4.00%. Rate of B: 410 divided by 9,000 equals 4.56%.
- Relative improvement: (4.56 minus 4.00) divided by 4.00, about +13.9%. An eye catching lift at first glance.
- Pooled rate: (360 plus 410) divided by 18,000 equals 4.28%.
- Standard error: the square root of 0.0428 times 0.9572 times (1 divided by 9,000 plus 1 divided by 9,000), about 0.00302.
- z score: (0.0456 minus 0.0400) divided by 0.00302, about 1.84.
- Two sided p-value: about 0.066.
With a p-value of 0.066, above the 0.05 threshold, and a 95% confidence interval on the difference running from roughly minus 0.04 to plus 1.15 percentage points, crossing zero, the result is not statistically significant, despite a raw relative lift of nearly 14%. This is exactly the scenario where declaring a winner early, because the GA4 table “looks good”, becomes the most common mistake on the list above. The honest reading is: keep collecting, or accept that at this sample size the observed difference is still compatible with noise. Check the same arithmetic in the calculator below by pasting the numbers exported from your own report:
Two-sided two-proportion z-test. "Not significant" almost always means not enough sample, not that the versions are equal.
Make this automatic with Donnu
The work this guide covered is real engineering: designing the exposure event, guarding it so it fires once and at the right moment, registering dimensions before the test starts, building the Exploration and then not fooling yourself with sampling or peeking. GA4 does none of it for you, and a mistake in any one step can quietly poison weeks of data.
Donnu handles the other half of the problem. The stable 50/50 split and the honest significance calculation happen inside the tool itself, at the moment of assignment, without a hand built impression event or an Exploration report standing between you and the verdict. GA4 stays useful as a complementary source of behaviour and revenue, but the decision about the test no longer depends on it. Start a free trial and let the split and the statistics be somebody else’s job.
Read also: GA4 and A/B Testing: The Complete Integration Guide · A/B Testing Statistical Significance · Common A/B Testing Mistakes and Validity Threats · Leia em português
References
- Google Analytics Help. Custom dimensions and metrics. Scope, registration and the 50 event-scoped dimension limit on standard properties. support.google.com/analytics/answer/10075209.
- Google Analytics Help. About data sampling. The 10 million event query threshold on standard properties and the sampling indicator on Exploration reports. support.google.com/analytics/answer/13331292.
- Google Analytics Help. Explorations. Building free form reports with custom dimensions in rows. support.google.com/analytics/answer/7579450.
- Google Analytics Help. Sunset of Google Optimize. Official notice of the 30 September 2023 retirement. support.google.com/optimize/answer/12979939.
- Google Analytics Help. BigQuery Export. Daily event limits on the free export tier. support.google.com/analytics/answer/9358801.
- Google Tag Manager Help. Data layer. Reference for
dataLayer.pushand custom event triggers. developers.google.com/tag-platform/tag-manager/datalayer. - Kohavi, R., Tang, D. and Xu, Y. Trustworthy Online Controlled Experiments: A Practical Guide to A/B Testing. Cambridge University Press, 2020. Supporting material at experimentguide.com.
Frequently asked questions
- Does GA4 have a native A/B testing report?
- No. GA4 has no experiment object, no variation dimension and no significance calculation of its own. Since Google Optimize was retired in September 2023, the native integration that used to populate those reports no longer exists. Everything you see about an A/B test inside GA4 today is something you built: a custom event that says which variation the user saw, two custom dimensions registered from that event, and an Exploration report that groups conversions by one of them. GA4 stores and slices the data; it does not decide the winner.
- Should I send the variation as an event parameter or as a user property?
- Send it as an event parameter on the impression event, and register it as an event-scoped custom dimension. A user property is overwritten by the last value it receives, so a visitor who is exposed to two different experiments, or who is reassigned after clearing storage, ends up labelled with only the most recent value, which quietly reassigns their earlier conversions to the wrong variation. Event scope keeps each event attached to the variation that was actually on screen when it fired.
- When exactly should the impression event fire?
- After the variation has been applied to the page, never before. If the event fires on page load and the variation is applied a few hundred milliseconds later, every visitor who leaves during that window is counted as exposed to a variation they never saw. The bias is not symmetrical either, because the variation branch usually carries the extra work, so it accumulates more of those phantom exposures and its denominator inflates. Fire the event from the same code path that finishes rendering the variation.
- Why do my GA4 numbers not match the numbers in my testing tool?
- They almost never match exactly, and small gaps are expected rather than alarming. The four usual causes are consent mode, which suppresses part of the traffic in GA4 but not in a tool that runs before the banner; ad blockers, which block the GA4 endpoint at a higher rate than a first-party snippet; different counting units, since GA4 counts users and sessions while the testing tool counts assigned visitors; and different attribution windows for the conversion. Use GA4 for behaviour and segmentation, and read the verdict where the assignment itself lives.
- Does GA4 sampling affect my A/B test reading?
- It can, in Explorations on high-volume properties. Google documents that standard properties apply sampling to Exploration reports above roughly 10 million events for the selected date range, and shows a sampling indicator at the top of the report. A sampled number is an estimate with its own error, which is not a safe input for a significance calculation. If the indicator appears, shorten the range, narrow the segment, or export the raw events through the BigQuery link and count them yourself.
- How many custom dimensions do I need for this?
- Two are enough, and two is what you should use: one for the experiment identifier and one for the variation identifier. Keeping them separate lets you run several experiments at once and still filter one at a time. It also matters because standard GA4 properties are limited to 50 event-scoped custom dimensions, so a scheme that creates a new dimension per experiment exhausts the quota quickly and forces you into cleanup work that breaks historical reports.