What each provider receives
This page lists, for each built-in provider, which script or SDK it loads,
which vendor calls each hook makes, and what personal data reaches the vendor
under which consent. Options and setup are covered in
Providers, and event name translation in
Events. The source of each provider is in
src/providers/<name>.ts.
Rules that apply to every provider
Section titled “Rules that apply to every provider”The analytics manager drives every provider through the same contract (see Lifecycle):
| Hook | When it runs |
|---|---|
setup |
Once, whatever the consent: when the instance is created in the browser, or on the first lifecycle pass after boot() if enabled was false then or the provider was registered later. |
consent |
On every lifecycle pass (boot, each consent change, each registration after boot), for every provider, before load. Providers that are not allowed to run still receive it, which is how vendor consent modes learn about a denial. |
load |
Once, on the first pass where the provider’s requirement is met, or at boot with loadBeforeConsent. With defer, later; calls made in the meantime are held, up to 100. |
page, track, identify, group, error |
Only while the provider is active, which means its requiredConsent is met. |
teardown |
When the requirement stops being met, when the provider is unregistered, and on destroy(). |
Scripts already executed cannot be unloaded. A withdrawal therefore means that ConsentLayer stops calling the provider, plus whatever the vendor’s own consent switch or opt-out does.
Providers receive userId under any consent they run with, and email and
phone only with marketing consent (see
Identity storage).
| Provider | id |
requiredConsent |
loadBeforeConsent |
defer |
teardown |
|---|---|---|---|---|---|
| GA4 | ga4 |
analytics, or none in advanced mode |
option, true in advanced mode |
— | — |
| Google Tag Manager | gtm |
none, or the option |
— | — | — |
| Meta Pixel | meta |
marketing |
option | — | — |
| PostHog | posthog |
none (cookieless) or analytics |
— | option | — |
| Sentry | sentry |
none |
— | option | — |
| Contentsquare | contentsquare |
analytics |
— | — | yes |
| Hotjar | hotjar |
analytics |
— | — | yes |
| Vercel Analytics | vercel |
none |
— | — | — |
| TikTok Pixel | tiktok |
marketing |
option | — | — |
A dash means the provider does not set that field. The id is the value to
use in only and the providerId in onError.
ga4({ measurementId, loadBeforeConsent?, consentMode? }) from consentlayer/providers/ga4.
The value is trimmed. An undefined, empty or whitespace-only
measurementId returns a stand-in plugin with only a setup hook that logs
the skip in debug mode. Any other value that does not match G- followed by 4 to 16 uppercase letters or digits throws.
| Hook | Behavior |
|---|---|
| Script | https://www.googletagmanager.com/gtag/js?id=<measurementId> |
setup |
Creates window.dataLayer and window.gtag if absent. Unless a googleConsentMode() adapter set its window flag first, calls gtag('consent', 'default', …) with every key denied except security_storage. Then gtag('js', new Date()), and in advanced mode config. |
consent |
Without the adapter, gtag('consent', 'update', …) mapped as below, skipped while the visitor has never chosen so the defaults stay in force. In basic mode, on the first pass with analytics granted, also gtag('config', measurementId, { send_page_view: false }), once per measurement ID per page. In advanced mode, sets user_id and user_data from the identity while analytics is granted and clears them otherwise. |
load |
Injects gtag.js. A failed load is reported with scope provider-script-load. |
page |
gtag('event', 'page_view', { page_location, page_path, page_title }) |
track |
gtag('event', ga4EventName(name), { ...params, ...providers.ga4 }) |
identify |
gtag('set', { user_id }) when there is a user ID, then gtag('set', 'user_data', { email, phone_number }) with whichever fields are present. In advanced mode, only while analytics is granted. |
reset |
gtag('set', { user_id: null }) and gtag('set', 'user_data', {}) |
| Withdrawal | The consent hook sends the denied Consent Mode update and, while analytics is denied, expires the _ga, _ga_<id> and _gid cookies on the current host and every parent domain; the manager stops sending events. |
| Consent Mode key | Follows |
|---|---|
analytics_storage |
analytics |
ad_storage, ad_user_data, ad_personalization |
marketing |
functionality_storage |
preferences |
security_storage |
always granted |
In basic mode config waits for analytics consent on purpose. With
loadBeforeConsent, gtag.js is on the page from boot but has no config, so
it sends no cookieless pings before the visitor agrees. Advanced mode
configures the tag in setup and runs with requiredConsent: 'none', so
page and track reach gtag before consent and Google sends cookieless
pings while analytics_storage is denied.
Names. lead becomes generate_lead. Any character other than a letter,
digit or _ becomes _, so consentlayer.scroll_depth is sent as
consentlayer_scroll_depth.
Personal data. user_id with analytics consent. Email and phone go into
user_data only with marketing consent; the phone is prefixed with + if
needed, and gtag hashes both itself.
Google Tag Manager
Section titled “Google Tag Manager”gtm({ containerId, requiredConsent?, serverContainerUrl? }) from
consentlayer/providers/gtm. The value is trimmed. An undefined, empty or
whitespace-only containerId returns a stand-in plugin with only a setup
hook that logs the skip in debug mode. Any other value that does not match
GTM- followed by 4 to 12 uppercase letters or digits throws.
requiredConsent defaults to 'none': the container loads at boot, and its
tags are expected to follow Consent Mode. With a category, nothing loads until
that category is granted. The provider never sets loadBeforeConsent.
| Hook | Behavior |
|---|---|
| Script | https://www.googletagmanager.com/gtm.js?id=<containerId>, or <serverContainerUrl>/gtm.js?id=<containerId> when serverContainerUrl is set. The URL must be HTTPS or a relative path, and a trailing / is removed. |
setup |
Creates window.dataLayer and window.gtag if absent. Unless a googleConsentMode() adapter set its window flag first, calls gtag('consent', 'default', …) with every key denied except security_storage, shared with ga4() so the defaults go out once. |
consent |
Without the adapter, gtag('consent', 'update', …) mapped as for GA4, skipped while the visitor has never chosen. Whenever the consent changes, pushes { event: 'consentlayer_consent', consent } to dataLayer. Pushes the user ID as for identify below. |
load |
Once per container ID per page: pushes { 'gtm.start': Date.now(), event: 'gtm.js' }, then injects gtm.js. A failed load is reported with scope provider-script-load and the message Failed to load Google Tag Manager container <containerId>. |
page |
Pushes { event: 'page_view', page_location, page_path, page_title } |
track |
Pushes { ...params, ...providers.gtm, event: name }. The name is not translated. |
identify |
Pushes { event: 'consentlayer_identify', user_id } only while analytics is granted, and only when the user ID differs from the last one pushed. |
reset |
Pushes { event: 'consentlayer_identify', user_id: null } if a user ID was pushed before. |
| Withdrawal | The consent hook sends the denied Consent Mode update, pushes the new consent and clears the user ID. With the default 'none', the manager keeps sending page and track, and the container’s tags decide what to do under Consent Mode. With a category, the manager stops once the category is withdrawn. |
The Consent Mode keys follow the same table as GA4. The container can
trigger tags on the consentlayer_consent, consentlayer_identify and
page_view events, and on each event name passed to track.
Personal data. user_id with analytics consent, in the
consentlayer_identify event. Email and phone are never pushed. track
passes params through as they are, so anything you put in them reaches
the data layer.
Meta Pixel
Section titled “Meta Pixel”meta({ pixelId, locale?, loadBeforeConsent?, events? }) from
consentlayer/providers/meta. The value is trimmed; an undefined, empty or
whitespace-only pixelId turns the provider off, and the factory throws if
it is not an 8 to 20 digit string.
| Hook | Behavior |
|---|---|
| Script | https://connect.facebook.net/<locale>/fbevents.js, default locale en_US |
load |
Skipped if a pixel was already initialized on the page. Otherwise runs an inline bootstrap that defines the fbq queue, calls fbq('consent', 'grant' | 'revoke') from the current consent, then fbq('init', pixelId, advancedMatching); then injects fbevents.js. A failed load is reported with scope provider-script-load. |
consent |
fbq('consent', 'grant') with marketing consent, otherwise fbq('consent', 'revoke'). |
page |
fbq('track', 'PageView', {}, { eventID }) |
track |
fbq('track' | 'trackCustom', name, params, { eventID }), for the events Meta receives |
teardown |
Only if marketing consent is no longer granted: expires the _fbp and _fbc cookies on the current host and every parent domain. |
identify, reset, group |
Not implemented. |
Because consent is called before init in the bootstrap, a pixel loaded
with loadBeforeConsent starts revoked and sends nothing until marketing is
granted. page and track also check marketing consent themselves.
Names. Standard events map to Meta’s (purchase → Purchase,
add_to_cart → AddToCart; the full list is in Events).
Other names listed in events are sent with trackCustom under their own name. items becomes
content_ids, contents, content_type: 'product' and num_items;
search_term becomes search_string; providers.meta is merged last.
Personal data. Meta accepts advanced matching only in fbq('init'), so
it is taken from the identity at load time: em (email), ph (phone digits)
and external_id (user ID). The pixel hashes them. Email and phone are
present only if marketing consent was granted at that point. An identify()
after the pixel has loaded does not reach the pixel until the next page load;
the server route still sends it (see Server forwarding).
Withdrawal after matching. The pixel keeps init’s email and phone for
the lifetime of the page. If it was initialized with them and marketing is
then withdrawn, the provider keeps the pixel revoked and silent until the page
reloads, even if marketing is granted again.
Deduplication. Each page and track passes eventID, the same ID the
server route sends to the Conversions API.
PostHog
Section titled “PostHog”posthog({ apiKey, apiHost?, uiHost?, cookieless?, autocapture?, recording?, errors?, groupType?, defer? })
from consentlayer/providers/posthog. posthog-js is imported dynamically
in load.
| Hook | Behavior |
|---|---|
| Requirement | none with cookieless (default); analytics with cookieless: false |
load |
Imports posthog-js and calls init with the options below, then applies the latest consent. |
consent |
Stores the context. Once the SDK is loaded, applies it: without analytics, opt_out_capturing(); with analytics, opt_in_capturing(), then identify(userId) if there is one, then session recording if recording: true. |
page |
capture('$pageview', { $current_url, title }) |
track |
capture(name, { ...params, ...providers.posthog }) |
identify |
identify(userId, { ...traits, email? }), only with analytics consent and a user ID. |
group |
group(groupType ?? 'company', groupId, traits), only with analytics consent. |
reset |
reset(), then re-applies the current consent. |
error |
captureException(error, { level, ...tags, ...extra }), unless errors: false. |
teardown |
Not implemented; the consent hook has already opted out. |
init options
| Option | Value |
|---|---|
api_host |
apiHost, default https://us.i.posthog.com |
ui_host |
uiHost |
cookieless_mode |
'on_reject' when cookieless, otherwise unset |
opt_out_capturing_by_default, opt_out_persistence_by_default |
true |
autocapture |
autocapture, default false |
capture_pageview, capture_pageleave, capture_exceptions, capture_heatmaps, capture_dead_clicks |
false |
disable_session_recording, disable_surveys, advanced_disable_feature_flags, disable_external_dependency_loading |
true |
advanced_disable_flags |
true, or false with recording |
PostHog starts a recording only when the project’s remote config, fetched
from <apiHost>/array/<apiKey>/config when PostHog loads, has recording
enabled. advanced_disable_flags also turns that fetch off, so it stays on
with recording; advanced_disable_feature_flags still keeps feature flags
off. The session recorder (posthog-js/dist/posthog-recorder) is imported
only when recording: true and analytics consent is granted.
Cookieless mode. Before analytics consent PostHog is opted out, which in
on_reject mode means it counts events without cookies or storage. This
requires cookieless mode to be enabled in the PostHog project settings.
Events sent before consent are anonymous; no user identity is passed until
analytics is granted.
Names. Unchanged.
Personal data. User ID with analytics consent; email, as a trait on
identify, only with marketing consent as well.
Sentry
Section titled “Sentry”sentry({ dsn, environment?, release?, replay?, tracing?, tunnel?, defer? })
from consentlayer/providers/sentry. @sentry/browser is imported
dynamically in load, and Replay and tracing are separate chunks.
| Hook | Behavior |
|---|---|
| Requirement | none |
load |
Imports the SDK and calls init (below), then applies the latest consent. |
consent |
Stores the context. Once the SDK is loaded: setUser({ id, email }) with analytics consent and a user ID, otherwise setUser(null). With replay and analytics consent, adds the Replay integration the first time and calls its startBuffering() on every later application; without analytics consent, calls its stop(). With tracing, adds the tracing integration the first time analytics consent is granted. |
page, track, group |
Not implemented. |
identify |
Re-applies setUser and the integrations from the current consent. |
reset |
setUser(null) |
error |
captureException(error, { level, tags: { ...tags, source }, extra }) |
init options
| Option | Value |
|---|---|
dsn, environment, release, tunnel |
From the options |
dataCollection |
userInfo, cookies, httpHeaders and urlQueryParams false; httpBodies empty |
integrations |
Sentry’s defaults without GlobalHandlers |
tracesSampleRate |
tracing.sampleRate ?? 1 when tracing is on |
replaysSessionSampleRate, replaysOnErrorSampleRate |
replay.sessionSampleRate ?? 0.1 and replay.onErrorSampleRate ?? 1 when Replay is on |
beforeSendTransaction |
Drops every transaction while analytics consent is not granted |
Errors without consent. Error reports are treated as necessary for
operating the site, so they are sent under any consent, without IP address,
cookies, headers, bodies or query strings. Uncaught errors reach Sentry
through ConsentLayer’s own error and unhandledrejection listeners, which
is why Sentry’s GlobalHandlers integration is removed; this avoids
duplicates. Turn the listeners off with errors: false or
errors: { globalHandlers: false } on createAnalytics.
Replay and tracing require analytics consent. A tracing integration
cannot be removed once added, so after a withdrawal its transactions are
dropped by beforeSendTransaction. A grant after a withdrawal only buffers
Replay for errors; full session recording resumes on the next page load.
Personal data. User ID with analytics consent; email with analytics and marketing consent.
Contentsquare
Section titled “Contentsquare”contentsquare({ tagId }) from consentlayer/providers/contentsquare.
| Hook | Behavior |
|---|---|
| Script | https://t.contentsquare.net/uxa/<tagId>.js |
load |
Skipped if the tag was already initialized on the page. Otherwise defines the window._uxa command queue, remembers the current path and injects the tag. The tag sends the first page view itself. Script failures are ignored. |
consent |
With analytics consent, expires the _cs_optout cookie on the current host and every parent domain. |
page |
['trackPageview', path], with the query string and hash removed. The first page view after load is skipped if its path equals the path the tag loaded on. |
track |
['trackPageEvent', name]; params are not sent. purchase instead sends ['ec:transaction:create', { id: transaction_id, revenue: value, currency }] followed by ['ec:transaction:send']. |
identify |
['identify', userId] when there is a user ID |
reset |
['resetIdentity'] |
teardown |
['optout'], only if analytics consent is no longer granted and the tag was initialized |
['optout'] is Contentsquare’s opt-out command. Contentsquare has no web
command to opt back in, so when analytics is granted again the provider
deletes the _cs_optout cookie and collection resumes on the next page load.
See Contentsquare’s consent documentation.
Personal data. User ID with analytics consent. Email is never sent; Contentsquare advises against identifying visitors by email.
Hotjar
Section titled “Hotjar”hotjar({ siteId, version, reloadOnRevoke? }) from
consentlayer/providers/hotjar.
| Hook | Behavior |
|---|---|
| Options | siteId and version accept positive integers, as numbers or as strings of digits; surrounding whitespace is trimmed. Either one undefined, '' or whitespace turns the provider off; any other value, such as '1e3' or '12.0', throws when hotjar() is called. |
| Script | https://static.hotjar.com/c/hotjar-<siteId>.js?sv=<version> |
load |
Skipped if Hotjar was already initialized on the page. Otherwise defines a queueing window.hj and window._hjSettings = { hjid, hjsv } and injects the script. Script failures are ignored. |
page |
Not implemented; Hotjar tracks page views itself. |
track |
hj('event', name); params are not sent. |
identify |
hj('identify', userId ?? null, { ...traits, email? }) |
reset |
hj('identify', null, {}) |
teardown |
Only if analytics consent is no longer granted: expires every _hj* cookie on the current host and every parent domain, removes every key starting with hj or _hj from localStorage and sessionStorage, then reloads the page if reloadOnRevoke is set. |
Hotjar cannot be switched off once loaded: it keeps recording until the page
unloads. Clearing its storage prevents it from recognizing the visitor later,
and reloadOnRevoke: true also ends the current recording.
Personal data. User ID with analytics consent; email only when there is also a user ID and marketing consent is granted.
Vercel Analytics
Section titled “Vercel Analytics”vercel({ trackEvent }) from consentlayer/providers/vercel, where
trackEvent is track from @vercel/analytics.
| Hook | Behavior |
|---|---|
load |
Not implemented. Render Vercel’s <Analytics /> yourself; it also counts page views. |
track |
trackEvent(name, params) with params flattened: strings, numbers and booleans kept, null and undefined sent as null, Date values as ISO strings, objects and arrays dropped. |
| All other hooks | Not implemented. |
Personal data. None; identity is never passed.
TikTok Pixel
Section titled “TikTok Pixel”tiktok({ pixelId, loadBeforeConsent?, events? }) from consentlayer/providers/tiktok.
The value is trimmed; an undefined, empty or whitespace-only pixelId turns
the provider off.
| Hook | Behavior |
|---|---|
| Script | https://analytics.tiktok.com/i18n/pixel/events.js?sdkid=<pixelId>&lib=ttq |
load |
Skipped if a pixel was already initialized on the page. Otherwise runs an inline bootstrap, TikTok’s base code with the consent mode methods added and without its script injection and ttq.page(), that defines the ttq queue, calls ttq.grantConsent() or ttq.holdConsent() from the current consent, then ttq.load(pixelId); then injects events.js. A failed load is reported with scope provider-script-load. |
consent |
ttq.grantConsent() with marketing consent, otherwise ttq.revokeConsent(). |
identify |
ttq.identify({ email, phone_number, external_id }), when the identity has any of them. |
page |
ttq.page() |
track |
ttq.track(name, params, { event_id }), for the events TikTok receives |
teardown |
Only if marketing consent is no longer granted: expires the _ttp, ttclid and ttcsid* cookies on the current host and every parent domain. |
reset, group |
Not implemented. |
Because consent is held before ttq.load in the bootstrap, a pixel loaded
with loadBeforeConsent shares nothing until marketing is granted. page,
track and identify also check marketing consent themselves.
Names. Standard events map to TikTok’s (purchase → Purchase,
add_to_cart → AddToCart; the full list is in Events).
Other names are sent under their own name. items becomes content_ids,
contents, content_type: 'product' and num_items; search_term becomes
search_string and transaction_id becomes order_id; providers.tiktok is
merged last.
Personal data. email is trimmed and lowercased, phone_number is the
phone’s digits prefixed with +, and external_id is the user ID. The pixel
hashes them. Email and phone are present only with marketing consent.
Withdrawal after matching. If the pixel received an email or phone and marketing is then withdrawn, the provider keeps the pixel revoked and silent until the page reloads, even if marketing is granted again.
Deduplication. Each track passes event_id, the same ID the server
route sends to the Events API. ttq.page() takes no event ID, so the Events
API destination skips page views.