fix(fb-pixel): guard global fbq bootstrap so multiple pixel destinations don't conflict#3097
Conversation
…ons don't conflict
|
Thank you @sarmah-rup for contributing this PR. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughFacebookPixel now avoids resetting an existing global ChangesMeta Pixel Bootstrap Guard
Estimated code review effort: 2 (Simple) | ~10 minutes Sequence Diagram(s)sequenceDiagram
participant DestinationA
participant DestinationB
participant WindowFbq
DestinationA->>WindowFbq: init() checks if (!window.fbq)
WindowFbq-->>DestinationA: not set, create shim
DestinationA->>WindowFbq: register init command for pixelId A
DestinationB->>WindowFbq: init() checks if (!window.fbq)
WindowFbq-->>DestinationB: already set, skip shim reset
DestinationB->>WindowFbq: reapply flags and register init command for pixelId B
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
This PR fixes a device-mode Meta (Facebook) Pixel integration issue where configuring multiple pixel destinations on the same page causes Meta’s SDK to detect a conflicting fbq version and prevents subsequent pixels from initializing. It does so by guarding the one-time global fbq shim bootstrap to avoid reinitializing/overwriting the global state when init() is called multiple times.
Changes:
- Guard the global
fbqshim bootstrap inFacebookPixel.init()so it only runs once per page. - Add a unit test ensuring the global shim/queue state is preserved when initializing a second destination while still registering the second pixel.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| packages/analytics-js-integrations/src/integrations/FacebookPixel/browser.js | Adds a !window.fbq guard around the one-time fbq shim bootstrap to avoid conflicting reinitialization across multiple destinations. |
| packages/analytics-js-integrations/tests/integrations/FacebookPixel/browser.test.js | Adds a regression test covering multi-destination initialization behavior and shim preservation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (!window.fbq) { | ||
| window._fbq = function () { | ||
| if (window.fbq.callMethod) { | ||
| window.fbq.callMethod.apply(window.fbq, arguments); | ||
| } else { | ||
| window.fbq.queue.push(arguments); | ||
| } | ||
| }; | ||
|
|
||
| window.fbq = window.fbq || window._fbq; | ||
| window.fbq.push = window.fbq; | ||
| window.fbq.loaded = true; | ||
| window.fbq.disablePushState = true; // disables automatic pageview tracking | ||
| window.fbq.allowDuplicatePageViews = true; // enables fb | ||
| window.fbq.version = '2.0'; | ||
| window.fbq.queue = []; | ||
| window.fbq = window.fbq || window._fbq; | ||
| window.fbq.push = window.fbq; | ||
| window.fbq.loaded = true; | ||
| window.fbq.disablePushState = true; // disables automatic pageview tracking | ||
| window.fbq.allowDuplicatePageViews = true; // enables fb | ||
| window.fbq.version = '2.0'; | ||
| window.fbq.queue = []; |
There was a problem hiding this comment.
Good catch. I moved disablePushState and allowDuplicatePageViews out of the one-time guard so they run on every init(), including when window.fbq was already bootstrapped by the host page or an earlier destination. Only the shim creation and version/queue setup (the parts that cause the version conflict) stay guarded. Added a test covering the pre-existing fbq case.
| describe('FacebookPixel multiple destinations bootstrap guard', () => { | ||
| const mockAnalytics = {}; | ||
|
|
||
| beforeEach(() => { | ||
| delete window.fbq; | ||
| delete window._fbq; | ||
| }); |
There was a problem hiding this comment.
Fixed. I now capture the previous window.fbq and window._fbq in beforeEach and restore them in an afterEach, so this suite no longer leaks global state into the sibling describes.
Fixes #2587
Symptom
When two Meta Pixel destinations are configured in device mode (different Pixel IDs), the browser console logs:
Only the first pixel ends up initialized (confirmed with the Meta Pixel Helper extension); the second one does not fire.
Root cause
FacebookPixel.init()runs the globalfbqbootstrap unconditionally. Thefbevents.jsscript is loaded only once, but each destination'sinit()re-runs the shim setup and overwrites the already-loaded SDK: it reassignswindow._fbq, resetswindow.fbq.push/loaded/versionand, critically,window.fbq.queue = []. Meta's real SDK sees its ownfbqmutated back to the2.0bootstrap state and reports a version conflict, which breaks the second pixel.Fix
I wrapped only the one-time shim bootstrap in an
if (!window.fbq) { ... }guard, mirroring Meta's official snippet (if (f.fbq) return;). The per-destination calls stay outside the guard, so every destination still registers itself:fbq('set', 'autoConfig', ...), the advanced-mapping block,fbq('init', pixelId, ...)and theScriptLoader(...)call all run for each instance. The result is a single global shim with both pixels initialized and no conflict warning.Tests
Added a unit test in
packages/analytics-js-integrations/__tests__/integrations/FacebookPixel/browser.test.jsthat initializes one destination, marks the shim (window.fbq.version+ a queued event), initializes a second destination, and asserts the global shim and its queue are preserved while the second pixel still registers its owninit. The full FacebookPixel suite passes (9/9), and I verified the new test fails against the old unguarded code, so it guards against regressions.I'm happy to sign the CLA and to make any changes the team would like.
Summary by CodeRabbit