GA4 / dataLayer Tracking Modes
If your site already pushes events to a dataLayer (typically for GA4), the SDK can read from it instead of — or alongside — collecting its own events. Set this with data-tracking-mode (or trackingMode in b2mConfig). Unrecognized or empty values silently fall back to native, so a typo in the mode name won't throw an error — it just won't behave as expected.
native (default) | datalayer | hybrid | |
|---|---|---|---|
| SDK generates its own events? | Yes — all of them | No — none of them | Only names not already seen in the dataLayer |
| Listens to the dataLayer? | No | Yes | Yes |
| Platform tracker (Ticimax/Shopify/...) | Runs | Does not run | Runs |
| Auto click/scroll/engagement tracking | Runs | Does not run | Runs for names the dataLayer doesn't provide |
| Risk | Duplicate data if a dataLayer also exists | Data loss if the dataLayer has gaps | — |
Which Mode Should You Pick?
- No dataLayer, or it's unreliable / partially populated →
native(the default — don't set the attribute at all). - dataLayer is complete — everything that goes to GA4, including the full e-commerce funnel, is already pushed →
datalayer. The SDK never becomes a second source, so there's zero duplication risk. - dataLayer exists but has gaps — e.g.
purchaseis pushed butscroll_depthisn't →hybrid. The SDK fills in exactly the gaps.
How Hybrid Decides, Exactly
The distinction is per event name, and it's dynamic: the moment a given name is seen once in the dataLayer, the dataLayer owns that name for the rest of the page — the SDK stops generating it from its own sources (auto-track, platform tracker, SPA routing). Names never seen keep flowing from the SDK.
In practice: on the same page, purchase can come from the dataLayer while scroll_depth comes from the SDK — and if you later add scroll_depth to the dataLayer too, the SDK stops sending it automatically, with no configuration change needed.
If the dataLayer includes user_id, it overrides the SDK's own identity for that event. If you write to both the dataLayer and b2mSetUserId, the dataLayer value wins. On logout, either b2mSetUserId(null) or dataLayer.push({ user_id: null }) clears a sticky dataLayer identity.
data-event-groups is still required in every mode — the tracking mode decides where an event's data comes from, event-group gating decides which names are accepted at all. A name outside the enabled groups is dropped even if the dataLayer sends it.