Skip to content

Meta Pixel

The Meta Pixel is a snippet of JavaScript code that loads a small library of functions you can use to track Facebook ad-driven visitor activity on your website.

AppIntegrated
UK✅
DE✅

Meta Pixel requires the Pixel’s base code. An overview of the integration methods can be found via the links below:

An overview of the debugging tools can be found via the links below:

Before integrating Meta Pixel, note the following:

The @releafuk/analytics package provides a Meta Pixel plugin through the metaPixelProvider() factory function. The provider loads the pixel base code itself, so no Meta knowledge is needed in the application beyond its event mappings.

The provider declares requiredConsent: ["advertising"], so the analytics service only initialises it once advertising consent is granted, and re-runs init() if consent is granted later in the session.

The metaPixelProvider() factory accepts the config parameter that should satisfy the MetaPixelConfig type:

type MetaPixelConfig = {
pixelId: string;
enabled?: boolean;
consentMode?: "basic" | "advanced";
autoConfig?: boolean;
disablePushState?: boolean;
trackPageViewOnInit?: boolean;
nonce?: string;
untrackedPaths?: string[];
};
OptionRequiredDefaultDescription
pixelIdYes-The Meta Pixel ID from Events Manager. An empty string makes the provider a no-op.
enabledNotrueControls whether the provider loads and initialises the pixel. Use false to keep the provider registered but disabled for an environment — every method becomes a no-op, including after consent is granted.
consentModeNobasicControls whether markup() emits only the consent state, or the full bootstrap so the pixel loads before the visitor chooses. See Consent mode.
autoConfigNotrueMeta’s automatic configuration. Set to false to emit fbq('set', 'autoConfig', false, <pixelId>) and stop the pixel collecting button text and page metadata on its own. See Page view control.
disablePushStateNofalseEmits fbq.disablePushState = true before fbq('init'), which stops the pixel firing its own PageView on history.pushState / replaceState. Set it in single-page apps where the application decides when a page view happens (via analytics.page()), otherwise client-side navigations are reported twice or bypass untrackedPaths. See Page view control.
trackPageViewOnInitNotrueThe provider reports the load PageView itself at the end of init(), as Meta’s base code does, and analytics.page() is a no-op. Set false in an application that reports every page view through analytics.page(). See Who reports the page view.
nonceNo-CSP nonce added to the dynamically injected pixel script.
untrackedPathsNo-Path prefixes that must never report a PageView. See Page view control.
metaPixelProvider({
pixelId: import.meta.env.PUBLIC_META_PIXEL_ID,
enabled: import.meta.env.IS_META_PIXEL_ENABLED === "true",
untrackedPaths: ["/account", "/conditions/"],
});

Meta restricts sending sensitive data, including health information, through its Business Tools — see About Prohibited Information and the Meta Business Tools Terms. On a clinical site this makes page views the highest-risk signal: a URL such as /conditions/chronic-pain discloses a condition on its own, before any event payload is considered.

untrackedPaths gates every PageView the provider sends — both the one fired during init() and each later analytics.page() call. A matched path is suppressed for that page only; events sent through analytics.trackEvent() are unaffected.

List the exceptions rather than the pages you want tracked: tracking is the norm, and an allow-list quietly drops every new marketing page until someone remembers to add it.

metaPixelProvider({
pixelId: import.meta.env.PUBLIC_META_PIXEL_ID,
untrackedPaths: ["/account", "/questionnaire", "/conditions/"],
});

Matching is against PageInput.data.path and is shared through the isPathUntracked utility, so other providers can adopt the same exclusion semantics.

Each entry is a path prefix, anchored at the start of the path:

  • "/account" excludes /account and everything beneath it, but not /my-account-benefits
  • "/conditions/" excludes /conditions/chronic-pain while leaving the /conditions index page tracked — the trailing slash is what separates a subtree from the page above it

Prefixes rather than exact paths, because sensitive areas are route subtrees with dynamic segments (/conditions/[...slug], /account/[...page]) and an exact list would silently miss every new one. Prefixes rather than substring matching, because an unanchored "/account" would also suppress unrelated marketing pages that happen to contain the word.

A page with no known path is not tracked once untrackedPaths is set. data and data.path are both optional — analytics.page() with no argument gets a full payload derived from window.location, but analytics.page({ name: "Pricing" }) carries no data at all. The provider cannot confirm such a page is outside the exclusions, so it suppresses the PageView rather than risk reporting a clinical URL. Leaving untrackedPaths unset keeps the default of tracking every page.

Meta recommends leaving the snippet’s PageView call intact, so this is a deliberate divergence, and it has a cost: the snippet’s call runs while the document head is parsed, whereas the provider’s runs after consent is resolved. Some short-bounce visitors on allowed pages will be missed as a result.

The policy lives in the application because the set of sensitive routes differs per app. The provider owns only the mechanism, which is what lets one plugin serve both the UK and DE applications.

Two details make the gate effective:

  • The injected snippet does not fire a PageView. Meta’s published base code ends with fbq('track', 'PageView'), which would report the landing page before any application code runs — including a medical landing page. The provider omits it and fires a gated PageView at the end of init() instead, so the first page view is checked against untrackedPaths like every other one.
  • disablePushState: true stops the pixel reporting client-side navigations on its own. By default fbevents.js fires a PageView on every history.pushState, which the provider cannot gate. With the option set, the only PageViews are the ones the provider sends.
  • autoConfig: false stops the pixel collecting on its own. While automatic configuration is enabled, the pixel sends button text and page metadata independently of the provider, which untrackedPaths cannot intercept. Disabling it is recommended for any application serving clinical content.

Either the provider or the application reports page views — never both. trackPageViewOnInit picks which, and it follows the convention the other providers already use: ga4Provider ignores analytics.page() while send_page_view leaves the page view to gtag, postHogProvider while capture_pageview leaves it to PostHog.

true (default) — the provider reports. init() ends with a PageView, and because init() runs on every document load — at page load when consent is already saved, or inside updateConsent() when the visitor accepts — each load is reported exactly once. analytics.page() is a no-op for this provider, so an application may call it freely for other providers, after a consent banner or on a route change, without producing a second Meta PageView. This is the right setting for server-rendered sites.

false — the application reports. init() sends nothing and every PageView comes from analytics.page(): on load, after consent is granted, and on each client-side navigation. Pair it with disablePushState: true so the pixel does not also report history.pushState on its own. This is the right setting for single-page applications, where the document loads once and only the router knows a navigation happened.

metaPixelProvider({
pixelId: import.meta.env.PUBLIC_META_PIXEL_ID,
trackPageViewOnInit: false,
disablePushState: true,
});

The no-op is deliberate rather than something the application has to remember. A doubled PageView is not a visible error — it inflates landing-page metrics and audience sizes quietly, and Meta applies no eventID deduplication to PageView sent from the same pixel — so adding a provider that needs analytics.page() must not be able to regress this one.

fbq('consent', 'revoke') pauses the pixel, and must be issued before fbq('init') and on every page.

The provider emits Meta’s base code snippet verbatim, then appends the commands that have to precede initialisation:

<Meta's base code snippet, unmodified>
fbq.disablePushState = true; // only when disablePushState is true
fbq('consent', 'revoke'); // or 'grant'
fbq('set', 'autoConfig', false, '<pixelId>'); // only when autoConfig is false
fbq('init', '<pixelId>');

Appending is enough because the snippet defines fbq as a queue. Until fbevents.js finishes loading, every call is buffered and then replayed in source order, so the consent command is processed before the init regardless of when the library arrives. Meta’s own PageView call is the one line not carried over — see Page view control.

consentMode: "basic" (the default) contributes no markup at all. The whole script is injected by init(), which the analytics service only calls once advertising consent is granted, so nothing — not even the fbevents.js request — reaches Meta before the visitor chooses.

consentMode: "advanced" makes markup() emit that script for the application to render in the document head, so the pixel loads immediately in a revoked state and queues events, which lets Meta model conversions for visitors who decline. init() then issues fbq('consent', 'grant') and the gated PageView. It skips re-injecting the script when fbevents.js is already on the page; the snippet’s own if(f.fbq)return; guard makes a double injection harmless in any case.

updateConsent() maps the service’s advertising consent key onto fbq('consent', 'grant' | 'revoke'), and runs for every consent change, so withdrawing consent after granting it pauses the pixel.

Meta separates standard events from custom ones. The provider holds Meta’s standard event names and routes accordingly: a mapped event whose name is a standard event is sent with fbq('track', …), and anything else with fbq('trackCustom', …). Applications map to Meta’s vocabulary in their event definitions and need no provider-specific flag:

createEventDefinition(purchase, {
providers: {
metaPixel: (data) => ({
event: "Purchase",
params: { value: data.total, currency: data.currency },
}),
},
});

meta.eventId is forwarded as the eventID parameter on both PageView and tracked events, which is what Meta uses to deduplicate pixel and Conversions API events. An application sending the same conversion through both paths should use the same event ID for each.

  • Advanced matching. identify() is not implemented. Meta supports sending hashed customer data on fbq('init'), but that starts sending PII to Meta and is a decision for each application rather than a provider default.
  • Multiple pixels. The provider configures a single pixelId. Running several pixels on one page needs fbq('trackSingle', …) so events are not duplicated across them.
  • The <noscript> fallback. Meta’s base code ends with a <noscript> <img> that reports a PageView for visitors without JavaScript. The provider does not emit one. It would fire on page load with no JavaScript in the path, so it can respect neither the consent state nor untrackedPaths — on a clinical site that means a no-JS visitor to a condition page would send that URL to Meta before consenting. The lost coverage is a no-JS visitor population that cannot be tracked compliantly in the first place.
  • Conversions API. Server-side delivery is a separate provider; this one covers the browser pixel only.