> ## Documentation Index
> Fetch the complete documentation index at: https://docs.abtestly.com/llms.txt
> Use this file to discover all available pages before exploring further.

# GA4 export

> Cross-check experiment results in your Google Analytics 4 property using the official A/B-testing vendor event.

ABTestly pushes a Google-spec **`experience_impression`** event to your site's
`dataLayer` so you can cross-check experiment exposures (and run deeper
GA4-native analysis) inside your own GA4 property. The data lands in *your*
GA4 — ABTestly never sees it.

This is **off by default**. Enable it per site under **Site → GA4 Integration**.

<Note>
  ABTestly's built-in results remain the primary analysis surface (clean
  significance math, sample-ratio checks, per-variant lift with CIs). The GA4
  export is for cross-validation and for richer GA4-native explorations (e.g.
  conversion-by-variant for *one* experiment via the Segment recipe below, or
  unconstrained multi-experiment SQL in BigQuery).
</Note>

## How it works

When a visitor is bucketed into a running experiment, the snippet pushes the
Google official A/B-testing vendor event:

```js theme={null}
window.dataLayer.push({
  event: 'experience_impression',
  exp_variant_string: 'ABT-7-1',     // canonical combined dimension
  experiment_id: '7',                // per-org display number
  variant_id: '1',                   // variant index (0 = control)
  experiment_name: 'Change hero text' // optional, human-readable
});
```

The event name and `exp_variant_string` parameter follow [Google's official
spec](https://support.google.com/analytics/answer/12238558) for third-party
A/B-testing tools, which means our data plugs into standard GA4 A/B tooling
and Looker Studio templates designed for it. The `ABT-` prefix scopes the
dimension to ABTestly experiments.

The push is **consent-gated**: under Google Consent Mode v2 deferred mode, no
event fires until consent is granted. It also only fires for sites where the
GA4 toggle is on.

### Event parameters

| Parameter            | Description                               | Example            |
| -------------------- | ----------------------------------------- | ------------------ |
| `exp_variant_string` | Canonical combined dimension              | `ABT-7-1`          |
| `experiment_id`      | Per-org experiment number (string)        | `7`                |
| `variant_id`         | Variant index, `0` = control (string)     | `1`                |
| `experiment_name`    | Human-readable experiment name (optional) | `Change hero text` |

### Concurrent experiments

A visitor in N experiments fires N `experience_impression` events per page —
one per experiment. That is correct and required: it's what the per-experiment
Segment recipe below relies on. There is no single "the variant" for a
visitor when they're in multiple tests — that's a property of how A/B
attribution works, not a tool limitation.

## Setup (≈ 5 minutes)

The fastest path is the Google Tag Manager container template, which wires up
everything except your Measurement ID.

<Steps>
  <Step title="Import the GTM container">
    Download the template from **Site → GA4 Integration → Download GTM
    container**. In Google Tag Manager: **Admin → Import Container → choose a
    workspace → Merge**. It creates the four Data Layer Variables, the
    `experience_impression` trigger, and a GA4 event tag.
  </Step>

  <Step title="Point the tag at your GA4">
    Open the imported **GA4 - ABTestly experience\_impression** tag and set its
    Measurement ID to your `G-XXXXXXXXXX` (or reference your existing GA4
    configuration tag).
  </Step>

  <Step title="Register the Custom Dimensions in GA4">
    In GA4: **Admin → Custom definitions → Create custom dimension**. Create
    **event-scoped** dimensions matching the event parameter names:
    `exp_variant_string`, `experiment_id`, `variant_id`, and (optional)
    `experiment_name`.

    <Warning>
      **Not retroactive.** GA4 only captures dimension values from the moment a
      Custom Dimension is registered. Register these *before* you start
      experiments you plan to analyze in GA4, or you'll have impression events
      with empty dimension values.
    </Warning>
  </Step>

  <Step title="Publish and enable">
    Publish the GTM container, then turn on the **Export to GA4** toggle in
    ABTestly.
  </Step>
</Steps>

### Without Google Tag Manager

If you tag GA4 directly (gtag.js) instead of GTM, listen for the
`experience_impression` event and forward the parameters to GA4:

```js theme={null}
window.dataLayer = window.dataLayer || [];
const _push = window.dataLayer.push.bind(window.dataLayer);
window.dataLayer.push = function (item) {
  if (item && item.event === 'experience_impression') {
    gtag('event', 'experience_impression', {
      exp_variant_string: item.exp_variant_string,
      experiment_id: item.experiment_id,
      variant_id: item.variant_id,
      experiment_name: item.experiment_name
    });
  }
  return _push(item);
};
```

You still need to register the matching **event-scoped** Custom Dimensions in
GA4 (step 3 above).

## Verifying it works

1. Open your site with GA4's **DebugView** active (or the GTM Preview pane).
2. Trigger an experiment exposure (load a page where a running test targets
   you).
3. You should see an `experience_impression` event with `exp_variant_string`,
   `experiment_id`, `variant_id`, and `experiment_name`.

Custom Dimensions can take up to 24–48 hours to populate in standard reports;
DebugView shows them immediately.

## GA4 numbers vs your ABTestly dashboard

If you compare the `experience_impression` count in GA4 to the participant
count on your ABTestly dashboard, GA4's number will usually be **higher**.
That's expected — the two counts measure different things.

* **`experience_impression` (GA4)** is an *exposure event*. It fires when a
  visitor sees an experiment on a qualifying page view. GA4 counts events.
* **Participants (ABTestly dashboard)** are *distinct visitors*. Each visitor
  is counted **once per experiment**, no matter how many times they view the
  tested page.

So a visitor who views the tested page three times in a session generates
three `experience_impression` events but is one participant. This is by
design, not a discrepancy.

<Note>
  **Compare variants in GA4 with rates, not counts.** For a variant
  comparison inside GA4, analyse **conversion rate per variant** —
  conversions ÷ exposures (or conversions ÷ users, when using a User
  segment) — segmented by `exp_variant_string`. Rates are robust to the
  events-vs-participants difference; raw impression counts are not. The
  Segment recipe below sets this up correctly.
</Note>

For an apples-to-apples *participant* count in GA4, build the Segment
recipe below with **User segments** rather than Session segments — GA4's
user join gives you a per-visitor view that matches the way ABTestly
counts participants.

## Analyzing one experiment in GA4 (the Segment recipe)

<Note>
  **Why you can't just put `exp_variant_string` as a dimension against a
  conversion metric.** GA4 event-scoped Custom Dimensions populate only on
  events that carry the parameter. Your conversion events (signup, purchase,
  …) don't carry `exp_variant_string`, so a cross-tab "signup count by
  variant" reads `(not set)`. This is a GA4 mechanic, not a bug. The correct
  approach — used by every other vendor that follows this spec — is **Segments
  in an Exploration**.
</Note>

### Recipe — one variant per segment, then compare

To break down a conversion event by variant for **one experiment** (say,
display number 7), do this once per variant:

<Steps>
  <Step title="Open Explore → Free form">
    In GA4: **Explore → + → Free form**.
  </Step>

  <Step title="Create one Segment per variant">
    Click **Segments → +** and choose **User segment** (preferred — survives
    across sessions when User-ID or Google Signals is on) or **Session
    segment** (single-session). Add an **Include** condition:

    *Event* `experience_impression` **with parameter** `exp_variant_string`
    **exactly matches** `ABT-7-0` (the control).

    Save as **"Exp 7 · Control"**. Repeat for each variant — `ABT-7-1`,
    `ABT-7-2`, etc.
  </Step>

  <Step title="Build the comparison">
    Drop the segments into the **Segment comparisons** slot. Drop your
    conversion event count (or revenue) into **Values**. The table now shows
    that metric per variant — the right way.
  </Step>
</Steps>

### What this method gives you

* A conversion metric (signups, revenue, anything in GA4) attributed per
  variant for *one* experiment, using GA4's session/user join — the same
  attribution GA4 uses for everything else.
* Works for any event the customer already tracks in GA4 — no need to forward
  the variant onto every conversion event.

### Caveats — these are GA4 mechanics, not ABTestly bugs

* **Not retroactive.** Custom Dimensions only capture data from registration
  forward. Register them before you need to analyze.
* **One experiment at a time.** A visitor in N experiments carries N
  `exp_variant_string` values; there is no single "the variant" for a
  conversion event. Build one set of Segments per experiment. For across-
  experiment analysis at scale, use [BigQuery](./ga4-bigquery).
* **Cross-device / cross-session attribution.** GA4's "user" is per-browser-
  cookie unless you have [User-ID](https://support.google.com/analytics/answer/9213390)
  set or Google Signals on. Without one of those, a user who is exposed on
  desktop and converts on mobile counts as two users.
* **4-comparison limit in standard reports.** Standard reports cap at four
  comparisons. Experiments with 5+ variants must use Explorations (Segment
  comparisons handle more).
* **`(other)` cardinality rollup.** GA4 collapses high-cardinality dimension
  values into `(other)` in standard reports for properties with many running
  experiments. The Segment recipe avoids this (it filters to a specific
  string), which is another reason to prefer it over broad dimensional
  cross-tabs.

## When you need more — BigQuery

For power users — many concurrent experiments, exact attribution across
overlapping tests, or no dimension/audience caps — pipe GA4 to BigQuery and
join impressions to conversions on `user_pseudo_id` with the parameterized
SQL we ship. See [GA4 BigQuery analysis](./ga4-bigquery).

## Importing GA4 goals

Re-using an existing GA4 conversion as an experiment goal is on the roadmap
but not yet available. If you need it, let us know at
[support@abtestly.com](mailto:support@abtestly.com) and we'll prioritise it.

***

## Start testing

<Card title="Create a free ABTestly account" icon="flask" href="https://app.abtestly.com/sign-up" horizontal>
  The free tier covers 3,000 monthly tracked users and one active experiment, no credit card. Edge-served snippet, with sample size and confidence intervals shown on every result.
</Card>
