The Bloomreach Engagement Console Error You Probably Don't Know You Have

Teal cover. Headline — Fix window.ga on GA4-Only Sites. Right side — a red browser-console TypeError reading window.ga is not a function, above a one-line JavaScript shim that silences it. Subheadline — The Bloomreach console error you missed.

A legacy Universal Analytics integration still enabled by default in many Bloomreach Engagement setups throws a recurring console exception on GA4-only sites. Here's how to spot it and turn it off.

The error

Open DevTools → Console on any site running Bloomreach Engagement (formerly Exponea) and you may see this:

Uncaught Error: Maximum number of retries reached while waiting for window.ga 
to appear (already waited for 5500ms). window.gtag is available but it does 
not appear to load window.ga anymore!
    at ze (https://api.exponea.com/js/exponea.min.js)
Uncaught Error: Maximum number of retries reached while waiting for window.ga 
to appear (already waited for 5500ms). window.gtag is available but it does 
not appear to load window.ga anymore!
    at ze (https://api.exponea.com/js/exponea.min.js)
Uncaught Error: Maximum number of retries reached while waiting for window.ga 
to appear (already waited for 5500ms). window.gtag is available but it does 
not appear to load window.ga anymore!
    at ze (https://api.exponea.com/js/exponea.min.js)

It fires every session. On long-open tabs, it fires repeatedly.

The error looks alarming but it is not breaking anything the user can see. Page rendering is fine. Event tracking is fine. Session tracking is fine. What is broken is one specific feature: the Bloomreach Engagement SDK's attempt to stitch customer records to Google Analytics using the GA client ID.

What is happening

The Bloomreach Engagement SDK has a built-in option called Track GA Cookies. When enabled, the SDK looks for the legacy Universal Analytics global window.ga on the page, reads the GA client ID from the _ga cookie, and attaches it as a customer identifier in Bloomreach Engagement.

This was useful when Universal Analytics was the dominant Google Analytics version. It let marketing teams stitch together a unified customer record across Bloomreach data and Google Analytics data without extra engineering work.

The problem: standard Universal Analytics properties stopped processing data on July 1, 2023. Universal Analytics 360 got a one-time extension and stopped on July 1, 2024. On a GA4-only site, window.ga does not exist. GA4 uses a different global (gtag), different cookies, and a different client ID format.

The SDK still runs the legacy check. It polls for window.ga for roughly 5.5 seconds (observed in the minified SDK; the exact retry window is an implementation detail that could change between SDK versions). When window.ga does not appear, the SDK throws the error you see in the console. The rest of the SDK continues working, but the exception surfaces in the console and in any error monitoring you have wired up (Sentry, Datadog RUM, LogRocket, etc.).

This is not edge-case speculation. Reading the Bloomreach Engagement JavaScript SDK source, the behavior is consistent: when the track.google_analytics config flag is true, the SDK checks for both window.ga and window.gtag. If window.ga is available, it calls window.ga.getAll() to retrieve the GA client ID. If only window.gtag is available, the SDK checks that it exists, then does nothing with it. There is no gtag('get', 'G-XXX', 'client_id', ...) call, no cookie parsing, no fallback logic. The gtag code path is effectively dead. The SDK polls, times out, throws the error, and the google_analytics soft ID is never populated on GA4-only sites. The feature is non-functional.

Why it matters

Three reasons worth fixing it.

Noise in your error monitoring. If you ship your site's client-side errors to a monitoring tool, every single page view generates this exception. Real errors that need attention get drowned out. Signal to noise drops.

It looks sloppy. If a prospect's engineer opens DevTools on your site to understand how your consent setup works (which is exactly what engineers do when evaluating a consultancy), a recurring console error undermines the impression. It is a small thing, but it is avoidable.

The integration is broken anyway. The feature it enables (stitching BR customer records to GA client IDs) does not work on GA4-only sites. Nothing of value is being delivered in exchange for the error. It is pure cost.

Performance impact is negligible. The polling is setTimeout-based, non-blocking, and contributes no measurable effect on Core Web Vitals. Do not fix this because you think it will speed up your site. Fix it because the console should be clean.

How to check if your site is affected

You can verify this from the outside, without access to the Bloomreach Engagement project.

  1. Open the site in Chrome

  2. Open DevTools → Console

  3. Let the page sit for 10 seconds

  4. Look for Uncaught Error ... exponea.min.js ... window.ga

If you also want to confirm the site is GA4-only (the condition under which this error is preventable):

typeof window.ga        // should return 'undefined'
typeof window.gtag      // should return 'function'
typeof window.exponea   // should return 'object'
typeof window.ga        // should return 'undefined'
typeof window.gtag      // should return 'function'
typeof window.exponea   // should return 'object'
typeof window.ga        // should return 'undefined'
typeof window.gtag      // should return 'function'
typeof window.exponea   // should return 'object'

If window.ga is undefined, window.gtag is a function, window.exponea is an object, and the console shows the retry error, the site is affected.

How to fix it

The fix is a toggle in one GTM tag. Five minutes.

  1. Open GTM → Tags

  2. Find the tag that initializes Bloomreach Engagement. It is usually a Custom HTML tag named something like Bloomreach Engagement Init, Exponea SDK, or just BR. The tag contains the exponea.start({...}) initialization call.

  3. Open the tag. Find the tag's built-in settings panel or the exponea.start() configuration inside the HTML.

  4. Find the option called Track GA Cookies (in some older snippets this is a property like google_analytics: true or identify_with_google_analytics: true inside the init config).

  5. Turn it off, or remove the option from the config entirely.

  6. Save the tag.

  7. Submit and publish the GTM container. Workspace changes alone are not enough, the container version must be published for the change to reach live visitors.

  8. Hard reload your site (Cmd+Shift+R / Ctrl+Shift+R) to bypass the cached GTM container.

  9. Check DevTools → Console. The error should be gone.

If you only see the tag as a Custom HTML block with no visible settings toggle, look for the track object inside the SDK configuration. The flag lives there, nested, alongside other track-level switches like visits:

// Bloomreach-generated snippet, typical structure
!function(e,n,t,i,r,o){ /* SDK loader ... */ }(
  document, "exponea", "script", "webxpClient", window, {
    target: "https://api-XXX.exponea.com",
    token: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    experimental: { non_personalized_weblayers: true },
    new_experiments: { mode: "async" },
    track: {
      visits: true,
      google_analytics: false   // ← add this line, or change from true to false
    }
  }
);
exponea.start();
// Bloomreach-generated snippet, typical structure
!function(e,n,t,i,r,o){ /* SDK loader ... */ }(
  document, "exponea", "script", "webxpClient", window, {
    target: "https://api-XXX.exponea.com",
    token: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    experimental: { non_personalized_weblayers: true },
    new_experiments: { mode: "async" },
    track: {
      visits: true,
      google_analytics: false   // ← add this line, or change from true to false
    }
  }
);
exponea.start();
// Bloomreach-generated snippet, typical structure
!function(e,n,t,i,r,o){ /* SDK loader ... */ }(
  document, "exponea", "script", "webxpClient", window, {
    target: "https://api-XXX.exponea.com",
    token: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    experimental: { non_personalized_weblayers: true },
    new_experiments: { mode: "async" },
    track: {
      visits: true,
      google_analytics: false   // ← add this line, or change from true to false
    }
  }
);
exponea.start();

The key lives inside track, not at the root of the config. If your snippet doesn't have a track block yet, add one. If it has visits: true already, add google_analytics: false as a sibling. Save, submit, publish the GTM container (or redeploy Framer if the snippet is in Custom Code), and hard reload the site.

Bloomreach's public JS SDK docs use token as the config key. In practice, some BR-generated snippets have historically used projectToken or placed options outside start() in a separate configuration block. Match the key name and placement your live snippet already uses; don't rename it. The part that matters for this fix is the google_analytics: false line, nested inside track.

What you lose

Nothing functional, because the feature was already broken on GA4-only sites. The google_analytics soft ID was never being populated. Turning the toggle off only removes the error.

If you do want GA4 identity in Bloomreach Engagement (a legitimate use case for any client doing cross-tool audience analysis, warehouse joins, or pushing BR events back to GA4 via the Measurement Protocol), you need to replace the legacy toggle with an explicit capture pattern. This is a separate implementation topic. We've written a companion manual covering the full setup: Capturing GA4 Client ID in Bloomreach Engagement.

The short version: read the _ga cookie in a GTM Custom HTML tag, strip the GA1.1. prefix, and call exponea.identify({ google_analytics: clientId }) after the BR init tag has fired. Consent-gated, sequenced, and takes five minutes to configure once you know the pattern.

Why the option still exists in Bloomreach Engagement

Some customers still run Universal Analytics in parallel with GA4, usually for historical reporting continuity or because a legacy integration depends on the old _ga cookies. For those setups, the Track GA Cookies option still does its original job. Removing it entirely from the SDK would break their deployments.

So the option remains, defaulted off in new setups but often still on in older ones that were configured before the UA sunset. If you are running Bloomreach Engagement on a site that was set up before mid-2023, this is worth checking.

The broader point

Platforms evolve. Features built for one version of a dependency keep running even after the dependency is gone, because removing them would break the minority of customers who still need them. This is how legacy settings accumulate, quietly, invisibly, producing errors that nobody owns.

On a Bloomreach Engagement site specifically, the Track GA Cookies toggle is the most common one we see. If your site has been running Bloomreach Engagement for more than two years and you migrated to GA4 at any point, check it.

Framework-specific note for Framer users

If your Bloomreach Engagement tag is loaded through GTM on a Framer site, the fix is exactly the same, find the tag in GTM, turn the option off, publish. The Framer side is not involved.

If the Bloomreach Engagement snippet is pasted directly into Framer's Custom Code (bypassing GTM), you will need to edit the init snippet there instead. Look for the google_analytics or similar key in the exponea.start() config and remove it.

Related manuals