Upgrading to 1.0
ConsentLayer 1.0 replaces @nicolopadovan/analytics-consent-manager, which
was published to GitHub Packages as 0.1.0, 1.0.0-rc.0 and 1.0.0-rc.1. This
guide covers the upgrade from any of those versions. The module-level
singletons, the separate consent engine and analytics manager, and the four
React components are replaced by one instance created with
createAnalytics() and one <AnalyticsProvider>. Event names follow GA4, the
bundled consent UI is removed, and browser-to-server forwarding uses a new
route handler.
Summary of breaking changes
Section titled “Summary of breaking changes”| Change | What to do |
|---|---|
| The package is renamed and published to npmjs.com | Install consentlayer and update every import |
| The engine, manager, default runtime and React components are replaced | Create one instance with createAnalytics() |
Storage keys, DOM events and globals use the consentlayer prefix |
Opt in to the legacy names or visitors see the banner again |
Auto-tracking events are renamed from acm.* to consentlayer.* |
Update dashboards or set names.prefix |
| An auto-tracking config that names a kind turns on only the kinds it names | Name every kind you want |
A click’s ui_target no longer falls back to its name or text |
Label tracked elements |
| Provider factories are renamed; GA4 and Meta no longer load before consent | Rename the providers |
The track* e-commerce helpers are removed; events use GA4 names |
Replace the helpers with track() and the param builders |
track() no longer requires analytics consent |
Check what each provider receives |
consentlayer/ui and its stylesheet are removed |
Build your own banner from the recipe |
“Reject non-essential” and “Save” store preferences: false |
Review consent behavior |
setConsent() rejects non-boolean values |
Validate consent you read yourself |
meta({ serverEndpoint }) and the /server helpers are removed |
Replace your server routes |
The validators and renderMetaPixelNoscriptHtml are removed |
Other removed exports |
| The plugin contract is renamed and its lifecycle changes | Update custom plugins |
Install the new package
Section titled “Install the new package”Remove the old package and install ConsentLayer:
pnpm remove @nicolopadovan/analytics-consent-managerpnpm add consentlayernpm uninstall @nicolopadovan/analytics-consent-managernpm install consentlayeryarn remove @nicolopadovan/analytics-consent-manageryarn add consentlayerbun remove @nicolopadovan/analytics-consent-managerbun add consentlayerReplace @nicolopadovan/analytics-consent-manager with consentlayer in every
import. ConsentLayer is published to the public npm registry, so you can
remove the @nicolopadovan:registry=https://npm.pkg.github.com line and its
token from .npmrc unless another package needs them.
react and react-dom are now optional peer dependencies, needed only for
consentlayer/react. The new PostHog and Sentry providers declare their SDKs
as optional peer dependencies too; see
Installation.
Replace the setup with one instance
Section titled “Replace the setup with one instance”createAnalytics() replaces createConsentEngine(), createAnalyticsManager()
and the default runtime that the module-level functions used. It creates the
consent engine, the local storage for consent and identity, and the manager,
and returns one object with the whole API. <AnalyticsProvider> replaces
<ConsentProvider>, <ConsentControllerProvider>, <AnalyticsManager> and
<AutoTracking>.
import { createAnalytics } from 'consentlayer'import { ga4 } from 'consentlayer/providers/ga4'import { autoTracking } from 'consentlayer/auto-tracking'
export const analytics = createAnalytics({ providers: [ga4({ measurementId: process.env.NEXT_PUBLIC_GA4_MEASUREMENT_ID })], autoTracking: autoTracking(), names: { legacy: true },})'use client'
import { AnalyticsProvider } from 'consentlayer/next'import { analytics } from '@/lib/analytics'
export function Providers({ children }: { children: React.ReactNode }) { return ( <AnalyticsProvider analytics={analytics}> {children} </AnalyticsProvider> )}<AnalyticsProvider> from consentlayer/next takes the route from the App
Router, so it replaces the routeKey={usePathname()} you passed before and
also counts query-string changes: a search or filter page that writes its
state to the URL sends a page view for each change. To keep one page view per
path, use <AnalyticsProvider> from consentlayer/react with
routeKey={usePathname()} instead. Remove any <Suspense> boundary you added
around the provider for useSearchParams(): it made every page inside it
render only on the client. The Next.js guide explains why. In
other React apps, keep <AnalyticsProvider> from consentlayer/react and
pass routeKey yourself.
Create the instance once, at module level, rather than inside a component.
Code outside React, such as app/global-error.tsx, imports the same instance,
and createAnalytics() logs a warning when a second instance on the page uses
different names.
Full before and after for a Next.js app
Before:
import { createAnalyticsManager, createConsentEngine, createLocalStorageConsentStorage, setDefaultConsentEngine, setDefaultRuntimeAnalyticsManager,} from '@nicolopadovan/analytics-consent-manager'import { createGa4Adapter } from '@nicolopadovan/analytics-consent-manager/providers/ga4'import { createMetaAdapter } from '@nicolopadovan/analytics-consent-manager/providers/meta'
export const consentEngine = createConsentEngine({ storage: createLocalStorageConsentStorage({ key: 'acm_consent' }),})
export const manager = createAnalyticsManager({ consentEngine, providers: [ createGa4Adapter({ measurementId: process.env.NEXT_PUBLIC_GA4_MEASUREMENT_ID! }), createMetaAdapter({ pixelId: process.env.NEXT_PUBLIC_FACEBOOK_PIXEL_ID!, serverEndpoint: '/api/meta', }), ],})
setDefaultConsentEngine(consentEngine)setDefaultRuntimeAnalyticsManager(manager)'use client'
import { AnalyticsManager, AutoTracking, ConsentControllerProvider, ConsentProvider,} from '@nicolopadovan/analytics-consent-manager/react'import { CookieBanner, CookieDialog } from '@nicolopadovan/analytics-consent-manager/ui'import '@nicolopadovan/analytics-consent-manager/ui/styles.css'import { usePathname } from 'next/navigation'import { consentEngine, manager } from '@/lib/analytics'
export function Providers({ children }: { children: React.ReactNode }) { const pathname = usePathname() return ( <ConsentProvider engine={consentEngine}> <ConsentControllerProvider> <AnalyticsManager manager={manager} routeKey={pathname} lazy={{ mode: 'idle' }} /> <AutoTracking manager={manager} routeKey={pathname} /> {children} <CookieBanner /> <CookieDialog /> </ConsentControllerProvider> </ConsentProvider> )}After:
import { createAnalytics } from 'consentlayer'import { ga4 } from 'consentlayer/providers/ga4'import { meta } from 'consentlayer/providers/meta'import { autoTracking } from 'consentlayer/auto-tracking'
export const analytics = createAnalytics({ providers: [ ga4({ measurementId: process.env.NEXT_PUBLIC_GA4_MEASUREMENT_ID }), meta({ pixelId: process.env.NEXT_PUBLIC_FACEBOOK_PIXEL_ID }), ], server: { endpoint: '/api/analytics' }, autoTracking: autoTracking(), names: { legacy: true },})'use client'
import { AnalyticsProvider } from 'consentlayer/next'import { ConsentBanner } from '@/components/consent-banner'import { analytics } from '@/lib/analytics'
export function Providers({ children }: { children: React.ReactNode }) { return ( <AnalyticsProvider analytics={analytics} boot={{ mode: 'idle' }}> {children} <ConsentBanner /> </AnalyticsProvider> )}ConsentBanner is the Consent UI recipe, and
/api/analytics is the route described in
Replace your server routes.
Removed APIs and their replacements
Section titled “Removed APIs and their replacements”| Removed | Replacement |
|---|---|
createConsentEngine(options) |
createAnalytics({ consent: { storage, adapters, initialConsent, broadcast } }) |
createAnalyticsManager(options) |
createAnalytics({ providers, identityStorage, enabled, debug, onError }) |
setDefaultConsentEngine, getDefaultConsentEngine, setDefaultRuntimeAnalyticsManager, getDefaultRuntimeAnalyticsManager |
Import the instance |
setDefaultIdentityStorage, getDefaultIdentityStorage, useDefaultIdentityStorage |
createAnalytics({ identityStorage }); the default is local storage |
registerProvider(adapter) |
providers: [...], or analytics.registerProvider(plugin) |
updateConsent(consent) |
analytics.consent.setConsent(consent), or analytics.consent.update(partial) to change some categories |
trackEvent(name, params, options) |
analytics.track(name, params, options) |
trackPage(payload) |
analytics.page(payload) |
enableAutoTracking(config) |
createAnalytics({ autoTracking: autoTracking(config) }), from consentlayer/auto-tracking |
newPageSession() |
analytics.newPageSession(); <AnalyticsProvider> calls it when routeKey changes |
saveIdentity({ email, phone, external_id }) |
analytics.identify(external_id, { email, phone }) |
getIdentity() |
No replacement; the identity is passed to plugins as ctx.identity |
defaultEventDestinations |
Events go to every provider; limit one event with { only: [...] } and a provider’s events with its events option |
defaultRuntimePreset |
getNames(), which returns the configured names |
<ConsentProvider>, <ConsentControllerProvider>, <AnalyticsManager>, <AutoTracking> |
<AnalyticsProvider analytics={analytics}> |
<AnalyticsManager lazy={...}> |
<AnalyticsProvider boot={...}>, with the same mode and timeoutMs |
useTracker() |
useAnalytics(), which returns track, page and newPageSession |
The routeKey and page props move to <AnalyticsProvider> from
consentlayer/react unchanged; the one from consentlayer/next sets
routeKey for you.
<AnalyticsProvider> reads stored consent, boots the instance and sends a page
view for each routeKey. Outside React, make the same calls once at startup:
await analytics.consent.bootstrap()await analytics.boot()useConsent() and useConsentController() keep their names.
useConsent() now also returns update(partial). Both useAnalytics() and
useConsent() work outside <AnalyticsProvider>, where they use the instance
createAnalytics() created. useConsentController() still needs a provider
above it, and now throws
useConsentController must be used within AnalyticsProvider without one.
Calls made before boot are no longer lost. track, page, identify and
group are held, up to 100 calls, and delivered in order once boot() has
run.
Keep stored consent
Section titled “Keep stored consent”The old package stored consent and identity under acm_consent and
acm_user_identity, and announced changes through the acm:consent DOM event
and the ACM_CONSENT global. ConsentLayer uses the consentlayer prefix and
does not read the old names by default, so every returning visitor would see
the banner again. Choose one of two options.
Migrate to the new names. Pass legacy: true to read the old keys:
createAnalytics({ providers, names: { legacy: true } })On first read, a value stored under acm_consent or acm_user_identity is
moved to consentlayer_consent or consentlayer_user_identity and the old key
is removed. While legacy is on, ConsentLayer also dispatches acm:consent,
sets ACM_CONSENT and ACM_openConsent, and reads and writes the __acm*
window flags, so scripts that listen for the old names keep working.
Keep the old names. If the key is published, for example in your cookie
policy, set the prefix instead. Every name, including the auto-tracking
events, is then derived from acm exactly as before:
createAnalytics({ providers, names: { prefix: 'acm' } })| Name | acm (before) |
consentlayer (default) |
|---|---|---|
| Consent storage key | acm_consent |
consentlayer_consent |
| Identity storage key | acm_user_identity |
consentlayer_user_identity |
| DOM event | acm:consent |
consentlayer:consent |
| Consent global | ACM_CONSENT |
CONSENTLAYER_CONSENT |
| Open-settings global | ACM_openConsent |
CONSENTLAYER_openConsent |
If you passed your own storage key to createLocalStorageConsentStorage(),
pass it as names: { consentStorageKey: 'your_key' }, or pass the storage
itself as createAnalytics({ consent: { storage } }). See
Names for every option.
Rename auto-tracking events
Section titled “Rename auto-tracking events”Automatic events are renamed whether or not legacy is on:
| Before | After | Name GA4 receives |
|---|---|---|
acm.page_session_start |
consentlayer.page_session_start |
consentlayer_page_session_start |
acm.scroll_depth |
consentlayer.scroll_depth |
consentlayer_scroll_depth |
acm.time_on_page |
consentlayer.time_on_page |
consentlayer_time_on_page |
acm.ui_click |
consentlayer.ui_click |
consentlayer_ui_click |
The GA4 provider now replaces every character other than a letter, digit or
underscore with _, because GA4 rejects such names. Update reports and
dashboards that filter on acm.*, or keep the old names with
names: { prefix: 'acm' }.
Auto-tracking is configured with the autoTracking option instead of the
<AutoTracking config> prop. Pass it autoTracking(config) from
consentlayer/auto-tracking, which takes the same AutoTrackingConfig, or
autoTracking() for the defaults. It still starts only once analytics
consent is granted.
A config that turns on a kind, with true or its options, now turns on only
the kinds it names; before, every other kind stayed on unless set to false.
A config that turns nothing on, such as { scroll: false }, still keeps every
other kind on.
A click’s ui_target is now the value of the data-track attribute or
aria-label, and 'unknown' without either. It no longer falls back to the
element’s name or text, so page content doesn’t reach your analytics. Add
data-track to the elements your reports group clicks by. See
Auto-tracking.
// Before: clicks in attribute mode, plus page sessions, scroll and time on page<AutoTracking config={{ clicks: { mode: 'attribute' } }} />// Now: name each kind you wantautoTracking: autoTracking({ clicks: { mode: 'attribute' }, pageViews: true, scroll: true, timeOnPage: true })Rename the providers
Section titled “Rename the providers”| Before | After |
|---|---|
createGa4Adapter({ measurementId }) |
ga4({ measurementId }) |
createMetaAdapter({ pixelId, locale, serverEndpoint, fetchImpl }) |
meta({ pixelId, locale }); see server routes |
createHotjarAdapter({ siteId, version }) |
hotjar({ siteId, version }) |
createTikTokConsentAdapter() |
tiktok({ pixelId }); see below |
createVercelAdapter({ trackEvent }) |
vercel({ trackEvent }) |
createShopifyAnalyticsAdapter(options) |
Removed. Its events went to a queue that no Shopify script reads. |
createShopifyCustomerPrivacyAdapter(options) |
shopifyCustomerPrivacy(options) from consentlayer/adapters/shopify; pass it as createAnalytics({ consent: { adapters: [...] } }) |
Each factory is still imported from consentlayer/providers/<name>. Four
providers behave differently:
- GA4 and Meta load after consent. The old adapters injected
gtag.jsandfbevents.jsat boot, before the visitor chose. They now load nothing until analytics (GA4) or marketing (Meta) consent is granted. PassloadBeforeConsent: trueto download the script at boot again. GA4 still sends nothing before analytics consent: itsconfigcall runs only after consent, so no Consent Mode pings are sent while consent is denied. The Meta Pixel loads with consent revoked. - Vercel runs without consent. Vercel Analytics sets no cookies, so
vercel()setsrequiredConsent: 'none'. To keep the old analytics gate, override it:{ ...vercel({ trackEvent }), requiredConsent: 'analytics' }. - Hotjar is deprecated. Hotjar accounts are moving to Contentsquare; use
contentsquare({ tagId })fromconsentlayer/providers/contentsquareonce yours has moved. On analytics withdrawal,hotjar()now deletes Hotjar’s cookies and storage, andreloadOnRevoke: truereloads the page to stop recording. - TikTok loads the pixel. The old adapter only forwarded consent to a
ttqyou loaded yourself.tiktok({ pixelId })loads the TikTok pixel after marketing consent and sends page views, events and advanced matching through it. Remove your own TikTok base code when you switch.
MetaPixelNoscript moved from consentlayer/providers/meta to
consentlayer/react, so the Meta provider no longer imports React. Its props
are unchanged.
PostHog and Sentry are new built-in providers. If you wrote your own PostHog
plugin, compare it with posthog({ apiKey }) in
Providers.
Replace the e-commerce helpers
Section titled “Replace the e-commerce helpers”The track* helpers sent Meta’s event names to every provider. They are
removed. Call track() with a GA4 event name instead; each provider
translates it, so Meta still receives ViewContent, AddToCart and the other
standard events. The param builders come from consentlayer/ecommerce.
| Before | After |
|---|---|
trackViewContent(product) |
track('view_item', productParams(product)) |
trackAddToCart(item) |
track('add_to_cart', productParams(item)) |
trackAddToWishlist(item) |
track('add_to_wishlist', productParams(item)) |
trackInitiateCheckout(cart) |
track('begin_checkout', cartParams(cart)); see the cart mapping below |
trackInitiateCheckout(product) (buy now) |
track('begin_checkout', cartParams({ lines: [product] })) |
trackAddPaymentInfo({ value, currency }) |
track('add_payment_info', { value, currency }) |
trackPurchase(order) |
track('purchase', orderParams(order)) |
trackLead(params) |
track('lead', params); GA4 receives generate_lead |
trackSearch(query, results) |
track('search', { search_term: query }) |
trackContact(method) |
track('contact', { method }) |
trackSubscribe(email) |
identify(undefined, { email }), then track('subscription_started') |
trackCustomizeProduct(product) |
track('customize_product', productParams(product)) |
trackEvent('ViewContent', params) or another Meta name |
The GA4 name, such as track('view_item', params) |
productParams() and orderParams() accept the same product and order shapes
as before: id, variantId, title, price: { amount, currencyCode },
quantity and category, and for orders orderId, value, currency,
tax, shipping, coupon and lines. handle and affiliation are no
longer read. cartParams() reads lines as products, so map a Shopify cart’s
merchandise first.
Mapping a Shopify cart to cartParams()
import { cartParams } from 'consentlayer/ecommerce'
analytics.track( 'begin_checkout', cartParams({ subtotal: cart.subtotal, lines: cart.lines.map((line) => ({ id: line.merchandise.product.id, variantId: line.merchandise.id, title: line.merchandise.product.title, price: line.merchandise.price, quantity: line.quantity, })), }),)When subtotal is omitted, value is the sum of price times quantity over the
lines.
Review what each provider receives
Section titled “Review what each provider receives”The event payloads change, so reports built on the old names or params need updating:
- GA4 receives GA4’s own event names with an
items[]array, for exampleview_iteminstead ofViewContentwithproduct_idandvariant_id. - Meta receives the same standard events as before, with
content_ids,contents,content_typeandnum_itemsderived fromitems[], instead ofproduct_idandvariant_id. Content IDs are the variant ID when there is one, otherwise the product ID.search_termis sent assearch_string. A Meta standard name passed as it is, such astrack('Schedule'), is still sent as a standard event. - Every provider receives an event ID (
event.id), generated unless you pass{ eventId }. Meta uses it aseventIDto deduplicate pixel and Conversions API events, including page views.
track() used to drop every event unless analytics consent was granted, on
top of each provider’s own requirement. It no longer does: each provider’s
requiredConsent alone decides, so Meta now receives events with marketing
consent and no analytics consent. To restore the old rule for one event, pass
{ requireAnalyticsConsent: true }. page(), identify() and group() also
check each provider’s consent now.
Identity changes too. identify() stores email and phone only once marketing
consent is granted, and keeps them in memory until then. Without marketing
consent, only the user ID is stored and shared with providers.
Build your own consent banner
Section titled “Build your own consent banner”consentlayer/ui is removed, with CookieBanner, CookieDialog,
ManageConsentTrigger and styles.css. Copy the
Consent UI recipe into your app; it is built on the same
useConsentController() hook the old components used. If you copied the
.acm-* markup or themed the --acm-* custom properties, move those styles
onto the new component.
To open the settings from outside React, call window.CONSENTLAYER_openConsent(),
or keep window.ACM_openConsent() with names: { legacy: true } or
names: { prefix: 'acm' }.
Consent behavior
Section titled “Consent behavior”Three behaviors of useConsentController() and the consent API changed:
- “Reject non-essential” denies preferences.
rejectNonEssential()storespreferences: false; it used to storetrue. If your app readsconsent.preferences, for example to set a language or theme cookie, visitors who reject now have it off. - “Save” stores the preferences toggle.
prefsnow includespreferences, andsavePreferences()stores its value instead of always storingtrue. It starts asfalseuntil the visitor turns it on. setConsent()validates its input. It rejects with aTypeErrorunlesspreferences,analyticsandmarketingare booleans. Previously a string such as'false'counted as granted. Convert values read from your own cookie or form before passing them.
TypeError: [consentlayer] consent must have boolean preferences, analytics and marketing, received {"preferences":true,"analytics":"false","marketing":false}.Replace your server routes
Section titled “Replace your server routes”The Meta adapter used to post each pixel event to serverEndpoint in its own
format, which your route converted with createMetaEventPayload() and sent
with sendMetaCapiEvent(). Both sides are replaced:
createAnalytics({ providers: [meta({ pixelId })], server: { endpoint: '/api/analytics' },})import { createAnalyticsHandler } from 'consentlayer/server'import { metaCapi } from 'consentlayer/server/meta'
export const POST = createAnalyticsHandler({ destinations: [ metaCapi({ pixelId: process.env.NEXT_PUBLIC_FACEBOOK_PIXEL_ID, accessToken: process.env.META_TOKEN, }), ],})| Before | After |
|---|---|
createMetaAdapter({ serverEndpoint, fetchImpl }) |
createAnalytics({ server: { endpoint } }) |
A route that calls createMetaEventPayload() and sendMetaCapiEvent() |
createAnalyticsHandler({ destinations: [metaCapi(...)] }) |
A route that forwards browser events with sendGa4MeasurementEvent() |
Delete it; gtag.js already sends them. Use sendServerEvent() with ga4Server(...) for server-only events such as webhooks |
apiVersion defaulting to v18.0 |
metaCapi({ apiVersion }), defaulting to v26.0 |
The browser now posts { id, name, params, consent, identity, url, only, providers }, which a route written for the old body rejects or misreads.
Forwarding now covers page views, which reach the Conversions API as
PageView, and every track() call, not only the Meta adapter’s. The browser
forwards only while analytics or marketing consent is granted, and
metaCapi() sends only events whose consent.marketing is true.
consentlayer/server no longer exports the low-level helpers:
createMetaEventPayload, sendMetaCapiEvent, createMetaGraphUrl,
extractMetaCookies, sanitizeMetaCustomData, sha256, assertSameOrigin,
parseJsonWithLimit, parseCookieHeader, ANALYTICS_MAX_BODY,
createGa4MeasurementPayload, createGa4CollectUrl and
sendGa4MeasurementEvent. The handler performs the same-origin check, the
body limit, the cookie parsing and the hashing itself. Hashing uses Web
Crypto, so the server entry points run on the Edge runtime. See
Server-side tracking.
Other removed exports
Section titled “Other removed exports”| Removed | Replacement |
|---|---|
isValidGa4MeasurementId, assertValidGa4MeasurementId |
None. ga4() throws for an invalid ID |
isValidMetaPixelId, assertValidMetaPixelId |
None. meta() throws for an invalid ID |
isHttpsOrRelativeUrl, assertHttpsOrRelativeUrl |
None. createAnalytics() throws for an invalid server.endpoint |
isValidEmail, isValidPhoneE164 |
None. identify() drops invalid emails and phone numbers |
renderMetaPixelNoscriptHtml(pixelId) |
<MetaPixelNoscript pixelId={pixelId} enabled /> from consentlayer/react |
ConsentEngine, ConsentEngineOptions, AnalyticsManager, AnalyticsManagerOptions types |
Analytics, ConsentApi and AnalyticsOptions |
ProviderAdapter, AnalyticsProviderContext types |
AnalyticsPlugin, PluginContext |
The storage factories (createLocalStorageConsentStorage,
createLocalStorageIdentityStorage and the memory variants) are still
exported from the package root. createEnvConfig is too, but it is
deprecated: pass process.env values to the providers directly, since a
provider whose ID is unset turns itself off.
Update custom plugins
Section titled “Update custom plugins”A provider you wrote yourself is now a plugin. Wrap it in definePlugin() from
consentlayer for type checking, and rename its hooks:
| Before | After |
|---|---|
ProviderAdapter |
AnalyticsPlugin |
AnalyticsProviderContext |
PluginContext |
init(ctx) |
load(ctx). Inject scripts here. It runs once, the first time consent allows it |
onConsentChange(ctx) |
consent(ctx) |
trackPage(payload, ctx) |
page(page, ctx). page.id is the page view’s event ID |
trackEvent(event, ctx) |
track(event, ctx) |
enabled(ctx) |
Removed. Leave the plugin out of providers, or express the condition as a requiredConsent function |
ctx.isBooted |
Removed. Hooks run only after boot |
event.options.eventId ?? createEventId(), metaEventIdFrom(event) |
event.id |
event.options.sendTo |
event.options.only. event.options.providers[yourId] holds params meant only for your plugin |
ctx.identity.external_id |
ctx.identity.userId. Identities stored under external_id are read as userId |
The lifecycle changes in three ways:
setupruns once per plugin, beforeconsentandloadand whatever the consent. In the browser it runs when the instance is created, for plugins passed tocreateAnalytics()whileenabledis on; otherwise it runs on the first lifecycle pass after boot. It must not load scripts, send requests or set cookies. Move that work toload.loadruns once. The oldinitran each time the provider became active again after a withdrawal.- A plugin without
requiredConsentwas active before consent, but itstrackEventwaited for analytics consent.requiredConsentis now required, and a plugin without it never runs: set it explicitly, to'none'for a plugin that needs no consent.
New optional hooks are identify(identity, traits, ctx), reset(ctx),
group(groupId, traits, ctx) and error(error, context, ctx). A plugin can
also set loadBeforeConsent and defer.
Errors from plugin hooks reach onError under new scopes:
| Before | After |
|---|---|
provider-init |
provider-load |
provider-consent-change |
provider-consent |
provider-track-page |
provider-page |
provider-track-event |
provider-track |
identity-validation |
Removed |
provider-setup, provider-teardown and provider-script-load keep their
names. The full list is in Troubleshooting.
checkPlugin() from consentlayer/testing runs a plugin through the manager
in a DOM test environment, such as jsdom or happy-dom, and returns the contract
violations it finds:
import { checkPlugin } from 'consentlayer/testing'
expect(await checkPlugin(myPlugin)).toEqual([])See Writing a plugin for the full contract.