Consenti

UI Widget — Advanced Configuration

new ConsentiSetup(config) accepts a single ConsentiConfig object. All top-level keys are optional — the widget works with an empty config object, auto-detecting the compliance group from the browser. New here? Start with the Configuration quick start instead — this page is the complete reference, every field with its default value.

Full configuration reference

Every available option shown with its default value.

Full config — every field shown
ts
import { ConsentiSetup } from '@consenti/ui'

const widget = new ConsentiSetup({
  // ── Compliance group (optional) ──────────────────────────────────────────────
  compliance: {
    type: 'opt-in',              // see Compliance groups table below
    geoDataProvider: undefined,  // custom function to resolve visitor country
    // Age gate is a per-profile dashboard setting now, not a widget config option —
    // see "UI Widget — Age Gate" guide. The widget reads it off the resolved profile automatically.
    tcf: {                       // see "TCF v2.3 Implementation Guide"; cmpId/cmpVersion
      enabled: false,            // must match the backend's tcf config
      cmpId: 0,
      cmpVersion: 1,
    },
    gpp: {                       // IAB GPP (US National section); must match the backend's gpp config
      enabled: false,
      cmpId: 0,
      cmpVersion: 1,
      mspaCoveredTransaction: false,
      mspaOptOutOptionMode: 0,   // 0 = not applicable | 1 = yes | 2 = no
      mspaServiceProviderMode: 0,
    },
  },

  // ── Core behaviour (optional) ───────────────────────────────────────────────
  core: {
    tenantId: 'my-site',         // logical identifier for this installation
    locale: 'en',                // BCP 47; falls back to language prefix, then 'en'
    dir: 'auto',                 // 'ltr' | 'rtl' | 'auto' — 'auto' derives from locale
    storage: 'cookie',           // 'cookie' | 'localStorage'
    cookieName: 'consenti_data', // cookie/localStorage key name; default: 'consenti_data'
    cookieDomains: '.example.com',
    cookieSigningKey: undefined, // set to HMAC-sign the consent cookie; unset = unsigned
    allowReceipt: true,
    disableCssTemplate: false,
    userId: 'server-assigned-uuid', // authenticated users only
    usePrebuiltProfiles: 'all',  // 'all' | [ComplianceGroupId, ...] — load pre-built profiles instead of resolving via the API
    cacheResolvedProfiles: true, // cache resolved profile in sessionStorage (1h TTL)
    console: ['error', 'warn'],  // log levels to emit; 'error' | 'warn' | 'info' | 'debug'
    theme: {
      colorBg: '#ffffff',
      colorText: '#1a1a1a',
      colorTextMuted: '#6b7280',
      colorPrimary: '#1565c0',
      colorPrimaryText: '#ffffff',
      colorSecondary: '#f0f4f8',
      colorSecondaryText: '#1a3460',
      colorBorder: '#e2e8f0',
      colorSecondaryBorder: '#1a3460',
      colorOverlay: '#04111f',
      colorAccent: '#d32f2f',
      colorAccentText: '#ffffff',
      fontFamily: 'system-ui, sans-serif',
      fontFamilyMono: 'ui-monospace, monospace',
      fontSizeBase: '14px',
      fontSizeHeading: '18px',
      fontSizeMultiplier: '1',
      fontWeightHeading: '600',
      lineHeight: '1.5',
      spacingXs: '5px',
      spacingSm: '8px',
      spacingMd: '16px',
      spacingLg: '24px',
      borderRadius: '8px',
      borderRadiusBtn: '4px',
      shadow: '0 4px 24px rgba(21, 101, 192, 0.14)',
      toggleBgOn: '#1565c0',
      toggleBgPartial: '#97c098',
      toggleBgOff: '#cccccc',
      toggleKnob: '#ffffff',
      toggleWidth: '52px',
      toggleHeight: '28px',
      zBanner: '9999',
      zOverlay: '9998',
      zModal: '10000',
    },
  },

  // ── Mount point (optional) ───────────────────────────────────────────────────
  rootEl: '#consenti-root', // CSS selector or HTMLElement; omit to use document.body

  // ── Dark mode (optional) ─────────────────────────────────────────────────────
  darkMode: false,               // true = apply dark colour tokens to the widget

  // ── Auto Initialize widget (optional) ────────────────────────────────────────
  autoInit: true,

  // ── Hide Powered By Consenti text from banner/modal (optional) ───────────────
  hidePoweredBy: true,

  // ── Backend API (optional) ──────────────────────────────────────────────────
  api: {
    enabled: true,
    baseUrl: 'https://your-site.com',
    authToken: '',
    tenantId: 'my-site',         // tenant identifier sent with API requests
    complianceGroup: 'opt-in',   // skip auto-resolution; always fetch this group's profile
    trustDomain: false,          // bypass domain allowlist check (dev/test only)
  },

  // ── Integrations (optional) ─────────────────────────────────────────────────
  utils: {
    gtm: {
      containerId: 'GTM-XXXXXX',
      dataLayer: 'dataLayer',
      events: [],             // [] = all events
      urlPassthrough: true,
      adsDataRedaction: false,
    },
  },

  // ── Frontend plugins (optional) ─────────────────────────────────────────────
  plugins: [],

  // ── Runtime profile overrides (optional) ────────────────────────────────────
  profileOverride: {
    mainBanner: { position: 'top' },
  },
})

compliance

Selects which Compliance Group the widget applies. When omitted, Consenti auto-detects the appropriate group from the browser's navigator.language and optional geo data.

KeyTypeDefaultDescription
typestringauto-detectedCompliance group key. Either one of the 8 built-in groups (see the table below), or an arbitrary string matching a profile authored in the dashboard against a customComplianceGroup instead of a built-in group — resolved the same way as a fixed built-in group (api mode required; no pre-built fallback exists for a custom group).
geoDataProviderWidgetCountryResolverFnundefinedCustom async function returning { country: string | null, region: string | null, confidence: number }, plus an optional complianceGroup?: string — when set, that group is used directly and the country/region jurisdiction-map lookup (including any overriddenRegions carve-out) is skipped entirely. Used to improve auto-detection when the default timezone/locale-based heuristic is not accurate enough, or to route into an operator-defined custom group. Called once per session. Standalone mode only (no api.enabled) — ignored, with a warning, when the API is enabled, since the server resolves the compliance group in that mode (configure a server-side compliance.geoDataProvider instead).
complianceMap'default' | string | ComplianceMapData'default'Overrides the country→compliance-group mapping. Only meaningful in standalone mode (no api.enabled) — ignored, with a warning, when api.enabled: true, since the server resolves the group in that mode. 'default'keeps the embedded map; a URL string is fetched and used as the map (the browser's own HTTP cache honors whatever Cache-Control/ETag the response sends); an inline object overrides specific countries directly. Invalid data from either source logs a warning and falls back to 'default'. Does not affect country/region detection itself — only the final group lookup.
tcfTcfWidgetConfigundefinedIAB TCF v2.3 client stub configuration — { enabled, cmpId, cmpVersion }, must match the backend's tcf config. When enabled, installswindow.__tcfapi. See the "TCF v2.3 Implementation Guide".
gppGppWidgetConfigundefinedIAB GPP (US National section) client stub configuration — { enabled, cmpId, cmpVersion, mspaCoveredTransaction, mspaOptOutOptionMode, mspaServiceProviderMode }, must match the backend's gppconfig. Sale/sharing opt-out flags are derived automatically from the resolved profile's cpraCategory-tagged cookies and the visitor's actual consent. When enabled, installs window.__gpp. See the TCF & GPP Registration Guide.

Compliance groups

typeModelCovered regulationsNotes
opt-inOpt-inGDPR, ePrivacy, UK GDPR, PECR, Switzerland revFADP, KVKK, PDPA-TH, Quebec Law 25, Saudi/UAE/Qatar/Bahrain/Oman PDPLBanner on first visit; all non-mandatory cookies denied until granted. Default when no type given and browser locale maps to EU/EEA.
opt-outOpt-outCCPA, US state lawsAll cookies default to granted; consent written silently; no banner unless user visits a "Do Not Sell" page.
opt-out-strictStrict Opt-outCPRA (California 2023)Supersedes CCPA. Opt-out for sale/sharing; opt-in required for sensitive data. GPC triggers both Do Not Sell and Do Not Share.
opt-in-dpdpaOpt-in (DPDPA)DPDPA (India 2023)Fiduciary name + grievance officer rendered in modal. Age gate required for children under 18. GPC signal ignored.
opt-in-chinaOpt-in (China)PIPL (China 2021)Separate consent required for each processing purpose. Cross-border transfer rules enforced.
opt-in-brazilOpt-in (Brazil)LGPD10 lawful bases; ANPD-enforced; parental consent gate for under-12.
general-privacy-consentGeneral consentPIPEDA (Canada), POPIA (South Africa), APPI (Japan), South Korea PIPA, and 30+ other jurisdictions with no strict cookie-banner lawFull-flexibility mode: no region-specific behaviours enforced. Configure the banner entirely via your profile.
notice-onlyNotice onlyInformational / no opt-in law appliesConsent written automatically as granted for all cookies. Banner shown once as a notice, no action required.
ℹ️This is the summary. For the full list of every country/region mapped to a group, the regulations behind each one, and how customComplianceGroup profiles work, see the Jurisdiction Coverage Map.
Using a custom geo resolver
ts
import type { WidgetCountryResolverFn } from '@consenti/ui'

// geoDataProvider only runs for compliance.type: 'auto' — a fixed type (e.g. 'opt-in')
// never calls it, since there's no group to detect.
const geoResolver: WidgetCountryResolverFn = async () => {
  const res = await fetch('/api/geo')
  const data = await res.json()
  return { country: data.country, region: data.region ?? null, confidence: data.confidence ?? 1 }
}

new ConsentiSetup({
  compliance: {
    type: 'auto',
    geoDataProvider: geoResolver,
  },
})

// Or skip the jurisdiction-map lookup entirely when your source already knows the group:
const consentModelResolver: WidgetCountryResolverFn = async () => {
  const res = await fetch('/api/geo')
  const data = await res.json()
  return {
    country: data.country,
    region: data.region ?? null,
    confidence: 1,
    complianceGroup: data.consentModel, // e.g. an operator-defined custom group id
  }
}

core

Controls widget behaviour, consent storage, and theming. All keys are optional.

Tenant & locale

KeyTypeDefaultDescription
tenantIdstringundefinedLogical identifier for this installation. Sent with API requests and used to namespace consent records when multiple Consenti instances share the same backend.
localestring'en'BCP 47 locale code, e.g. 'fr', 'fr-CA'. Resolution order: exact match → language prefix → defaultLocale.

GPC

There is no top-level widget config key for GPC — it's a per-profile setting, gpcMode, defaulted by the resolved compliance group and overridable via profileOverride or the dashboard's Profile Editor.

KeyTypeDefaultDescription
gpcMode (per-profile)'ignore' | 'honor' | 'strict'compliance-group dependentHow to handle the browser's Global Privacy Control signal.'ignore' = do nothing. 'honor' = deny listenGpc cookies and show the GPC banner variant once.'strict' = deny and write consent silently — no banner shown.opt-out and opt-out-strict default to 'honor'; every other built-in group defaults to 'ignore'.
ts
new ConsentiSetup({
  compliance: { type: 'opt-out-strict' },
  profileOverride: {
    gpcMode: 'strict', // deny silently, no banner shown
  },
})

Freezing trackers before the SDK loads

gpcMode is applied once the widget initializes — after its bundle has been fetched, parsed, and executed. On a page where GTM/gtag.js or another tag-loading script sits earlier in <head>, that script can still fire before Consenti gets a chance to deny it. buildSyncGpcSnippet() closes that window: it returns a tiny, dependency-free <script> string you place first in <head>, ahead of GTM/gtag.js and the Consenti bundle itself. It checks navigator.globalPrivacyControl synchronously and, if set, pushes the same denied-by-default Google Consent Mode v2 defaults Consenti's own gtm config pushes on init — so there's no gap for a GPC-flagged visitor.

ts
import { buildSyncGpcSnippet } from '@consenti/ui'

// Server-side (e.g. a Next.js layout, or any template that renders <head>):
buildSyncGpcSnippet() // default dataLayer name
buildSyncGpcSnippet({ dataLayerName: 'myDataLayer' }) // custom dataLayer name
html
<head>
  <!-- 1. Sync GPC freeze — first, before anything else that loads tags -->
  <script>(function(){if(typeof navigator!=="undefined"&&navigator.globalPrivacyControl===true){window.dataLayer=window.dataLayer||[];if(typeof window.gtag!=="function"){window.gtag=function(){window.dataLayer.push(arguments)}}window.gtag("consent","default",{ad_storage:"denied",analytics_storage:"denied",ad_user_data:"denied",ad_personalization:"denied",functionality_storage:"granted",personalization_storage:"denied",security_storage:"granted",ads_data_redaction:"true",url_passthrough:"false"});window.__consentiGpcPreFrozen=true}})();</script>

  <!-- 2. GTM / gtag.js -->
  <script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX"></script>

  <!-- 3. Consenti's own bundle, loaded normally -->
  <script src="https://cdn.jsdelivr.net/npm/@consenti/ui/dist/index.umd.js"></script>
</head>
ℹ️Harmless to run twice: Consenti's own gtm config pushes the identical defaults again once it initializes — Google's consent API just takes the latest value per key, so the second push is a no-op, not a conflict. This snippet only ever narrows the window between page load and Consenti initializing; it does not replace configuring gpcMode.

Profile resolution

KeyTypeDefaultDescription
usePrebuiltProfiles'all' | [ComplianceGroupId, ...]undefined'all' lazy-loads the pre-built profile chunk for any resolved compliance group; an array restricts pre-built loading to just those groups (others fall through to the API or profileOverride). Leave unset when api.enabled is your source of truth.
cacheResolvedProfilesbooleantrueWhen true, the resolved profile URL from the API's /resolve-profile endpoint is cached in sessionStorage with a 1-hour TTL, avoiding redundant network requests on every page load.

Storage

KeyTypeDefaultDescription
storage'cookie' | 'localStorage''cookie'Where consent is persisted in the browser.cookie works across subdomains when cookieDomains is set.localStorage is scoped to the exact origin and cannot be shared across subdomains. In API mode the server always issues its own cookie regardless.
cookieNamestring'consenti_data'Name of the consent cookie/localStorage key. Not switched automatically by detected region — set it explicitly if you want a different name, e.g. 'euconsent-v2' (the IAB TCF convention). That name is only meaningful for operators using the spec-correct binary TCF encoder (@consenti/api + the optional @iabtechlabtcf/core peer dependency) — it implies IAB's binary encoding, not Consenti's own simplified format.
cookieDomainsstringundefinedComma-separated domain list, e.g. '.example.com,.sub.example.com'. The first entry is used as the Domain attribute on the consent cookie, making it readable on all subdomains of that domain.
cookieSigningKeystringundefinedHMAC-signs the local consent cookie so tampering is detectable client-side. Unset = unsigned. Independent of the backend's own compliance.dataSigningHash, which signs server-stored consent records.
⚠️Client-side verification is inherently spoofable in standalone mode. cookieSigningKey ships inside the browser bundle for any deployment that uses @consenti/ui without the @consenti/api backend — that's the only place the key can live if there's no server to hold it. Anyone can read it out of your shipped JS and re-sign a forged cookie with it, so this check only detects accidental tampering (e.g. a stale value from an older config), not a motivated attacker. If you need consent records that hold up as evidence, sign them server-side instead withcompliance.dataSigningHash (@consenti/api), where the key never reaches the browser.

Visitors & receipts

KeyTypeDefaultDescription
userIdstringundefinedServer-assigned UUID for authenticated users. When set, replaces the browser-generated visitor ID. Combined with API mode, this enables cross-device consent synchronisation — consent granted on mobile is recognised on desktop.
allowReceiptbooleanfalseWhen true, a "Download consent receipt" checkbox appears in the preference modal footer. Checking it before saving triggers a JSON download containing a timestamped record of the user's choices.

CSS & logging

KeyTypeDefaultDescription
disableCssTemplatebooleanfalseWhen true, no <style> tag is injected at all. Use this when you provide your own stylesheet and want full control over every rule. All BEM class names still apply.
consoleArray<'error' | 'warn' | 'info' | 'debug'>['error']Log levels to emit to the browser console. Pass an empty array to suppress all output. Add 'debug' during development to trace profile resolution and consent storage.

core.theme

Inline CSS token overrides. Each key maps directly to a --consenti-* CSS custom property injected on the widget root element at runtime. You only need to set the values you want to change — unset keys keep their stylesheet default. For full CSS control, see the Themes & CSS guide.

KeyCSS variableDefaultDescription
colorBg--consenti-color-bg#ffffffBackground colour for banners and modals.
colorText--consenti-color-text#1a1a1aPrimary body text colour.
colorPrimary--consenti-color-primary#1565c0Primary button background and interactive accent colour.
colorPrimaryText--consenti-color-primary-text#ffffffText colour rendered on top of colorPrimary backgrounds.
colorSecondary--consenti-color-secondary#f0f4f8Secondary / ghost button background.
colorSecondaryText--consenti-color-secondary-text#1a3460Text colour for secondary buttons.
colorBorder--consenti-color-border#e2e8f0Borders and dividers.
colorAccent--consenti-color-accent#d32f2fBackground for accent-style buttons (destructive actions).
colorAccentText--consenti-color-accent-text#ffffffText colour on top of colorAccent.
fontFamily--consenti-font-familysystem-ui, sans-serifFont stack applied to all widget text.
fontSizeBase--consenti-font-size-base14pxBase font size for body content.
fontSizeHeading--consenti-font-size-headinginheritFont size for banner and modal headings.
fontSizeMultipliernone — computedunsetNot a passthrough CSS var — read once at init and used to multiply the computed --consenti-font-size-base/--consenti-font-size-heading pixel values in place. '1.1' = 10% larger throughout.
borderRadius--consenti-border-radius8pxBorder-radius for banner and modal containers.
borderRadiusBtn--consenti-border-radius-btn4pxBorder-radius applied to all button elements.
toggleBgOn--consenti-toggle-bg-on#1565c0Background of toggle switches in the ON (granted) state.
toggleBgOff--consenti-toggle-bg-off#ccccccBackground of toggle switches in the OFF (denied) state.
colorTextMuted--consenti-color-text-muted#949dabSecondary/de-emphasised text colour (footer metadata, helper text).
colorSecondaryBorder--consenti-color-secondary-border#1a2e4aBorder colour for elements on the secondary background.
colorOverlay--consenti-color-overlay#04111fBackdrop colour behind the preference modal.
fontFamilyMono--consenti-font-family-monoui-monospace, monospaceMonospace font (e.g. the metadata footer's consent ID).
fontWeightHeading--consenti-font-weight-heading600Font weight for banner/modal headings.
lineHeight--consenti-line-height1.5Base line height for body text.
spacingXs / spacingSm / spacingMd / spacingLg--consenti-spacing-{xs,sm,md,lg}5px / 8px / 16px / 24pxSpacing scale used throughout the banner/modal layout.
shadow--consenti-shadow0 4px 24px rgba(21,101,192,.14)Box shadow on the banner/modal container.
toggleBgPartial--consenti-toggle-bg-partial#97c098Background of toggle switches in a partial/mixed-consent state.
toggleKnob--consenti-toggle-knob#ffffffToggle switch knob colour.
toggleWidth / toggleHeight--consenti-toggle-{width,height}52px / 28pxToggle switch dimensions.
zBanner / zOverlay / zModal--consenti-z-{banner,overlay,modal}9999 / 9998 / 10000Stacking order for the banner, its overlay, and the preference modal.
Minimal theme override
ts
new ConsentiSetup({
  compliance: { type: 'opt-in' },
  core: {
    theme: {
      colorPrimary: '#7c3aed',      // purple accent
      colorPrimaryText: '#ffffff',
      borderRadius: '12px',
      borderRadiusBtn: '999px',  // pill buttons
      fontFamily: 'Inter, sans-serif',
    },
  },
})

api

Connects the widget to the Consenti backend. When enabled, consent records are posted to the API and the active profile is resolved via /resolve-profile. Disabled by default — the widget works fully offline without it.

KeyTypeDefaultDescription
enabledbooleanfalseWhen true, the widget calls /resolve-profile to find the best profile for the visitor and posts consent records to the API. Falls back to a pre-built profile if the API request fails.
baseUrlstringwindow.location.originRoot URL where @consenti/api is mounted. The widget appends /consenti/api/v1/... to this value. Set explicitly when the API is on a different domain. Always point this at the origin server — not a CDN domain. Every request under this base is dynamic (/resolve-profile geo-resolves per visitor, /consentwrites records) and can't be served from a CDN or cached at the edge. A CDN (CloudFront, etc.) can still front the resolved profile JSON itself when the backend's s3Api is enabled — see s3Api and the CloudFront worked example — but that's a separate path from this baseUrl.
authTokenstring''Sent as Authorization: Bearer <token> on every API request. Leave empty for public / unauthenticated access.
tenantIdstringundefinedTenant identifier sent with API requests. Required when the backend serves multiple tenants. Must match the tenant configured in the dashboard.
complianceGroupstringundefinedWhen set, skips the /resolve-profile auto-resolution call and always fetches the profile for this specific compliance group. Useful for regional deployments or A/B testing.
trustDomainbooleanfalseBypasses the domain allowlist check on the resolved profile. Only use during local development or trusted server-side rendering. Never set to true in production.
API mode — auto-resolve
ts
new ConsentiSetup({
  api: {
    enabled: true,
    baseUrl: 'https://consent.example.com', // API is on a subdomain
  },
  // compliance group resolved automatically per-visitor via /resolve-profile
})
API mode — fixed compliance group
ts
new ConsentiSetup({
  compliance: { type: 'opt-in' },
  api: {
    enabled: true,
    baseUrl: 'https://consent.example.com',
    complianceGroup: 'opt-in', // always fetch the GDPR-model profile
  },
})
ℹ️When api.enabled is true and the network request fails (offline, server error), the widget silently falls back to the pre-built profile for the detected compliance group, then to the built-in default. Consent submission retries are not automatic — use the events API to implement your own retry logic.

utils.gtm

Google Tag Manager / Google Consent Mode v2 integration. Setting utils.gtm to any object (even {}) turns on real Consent Mode signalling: a gtag('consent', 'default', …) call as soon as the widget initializes — before any tag can fire — and gtag('consent', 'update', …) on every consent submission. This uses the standard gtag stub-queue pattern, so it works whether your own gtag.js/GTM snippet loads before or after Consenti — you do not need gtag.js already on the page.

KeyTypeDefaultDescription
containerIdstringundefinedGTM container ID, e.g. 'GTM-XXXXXX'. When set, Consenti injects the GTM library itself — omit if you already load GTM/gtag.js separately (Consent Mode signalling still works either way, since it only depends on utils.gtm being configured, not on this field).
dataLayerstring'dataLayer'Name of the dataLayer array on window. Override only when your site uses a custom variable name (rare).
verbosebooleanfalseWhen true, additionally mirrors every consenti:* event (banner shown, modal opened, etc.) onto the dataLayer as a generic { event, content } push — for custom, non-Consent-Mode GTM triggers. Off by default, so the dataLayer only carries real consent signals.
eventsstring[][]Only relevant when verbose: true — narrows which event names get mirrored. An empty array (default) means all events. Has no effect on the core Consent Mode gtag('consent', …) calls, which always fire regardless.
urlPassthroughbooleanfalseCalls gtag('set', 'url_passthrough', true)alongside every consent update. Enables Google Consent Mode v2 "cookieless pings" — Google can model conversions even when ad_storage is denied.
adsDataRedactionbooleanfalseCalls gtag('set', 'ads_data_redaction', true) when ad_storage is denied, causing Google to redact identifying fields from ad pings.
GTM + Consent Mode v2
ts
new ConsentiSetup({
  compliance: { type: 'opt-in' },
  utils: {
    gtm: {
      containerId: 'GTM-XXXXXX', // omit if you load GTM/gtag.js yourself
      urlPassthrough: true,      // cookieless conversion modelling
      adsDataRedaction: true,    // redact ad pings when consent denied
    },
  },
})

plugins

An array of frontend plugin instances to initialise alongside the widget. Each plugin receives the widget's public API surface via its initialize(widget) method and can hook into consent events, inject DOM, or forward consent signals to third-party services.

ts
import { SegmentPlugin } from '@consenti/ui-plugin-segment'

new ConsentiSetup({
  compliance: { type: 'opt-in' },
  plugins: [
    new SegmentPlugin({ writeKey: 'YOUR_WRITE_KEY' }),
  ],
})

See the Plugins guide for the full plugin API and available first-party plugins.


profileOverride

Accepts a Partial<ResolvedProfile> that is deep-merged on top of the resolved profile after it has been loaded (from the API, a pre-built profile, a local ConsentiProfile, or the built-in default). Only the keys you supply are applied — everything else is left unchanged.

This is a runtime override only. It does not affect the stored profile. See the Advanced Profile reference for a full reference and examples.

Override banner position per page
ts
// Checkout page — move banner out of the way
new ConsentiSetup({
  compliance: { type: 'opt-in' },
  profileOverride: {
    mainBanner: { position: 'right-bottom' },
  },
})
Override buttons only
ts
new ConsentiSetup({
  compliance: { type: 'opt-in' },
  profileOverride: {
    mainBanner: {
      buttons: {
        'accept': { text: 'Accept',  style: 'primary',   action: 'custom', cookies: '*' },
        'decline': { text: 'Decline', style: 'primary', action: 'custom', cookies: '!' },
      },
    },
  },
})

Deleting a key with null

Setting a key to null removes it from the merged result instead of leaving the base value in place (JSON Merge Patch semantics). This is how you remove a single entry from a keyed map — a cookie category, a parameter — without repeating the rest of that map's contents. Omitting a key (or setting it to undefined) still means "leave the base value alone" — only an explicit null deletes.

Remove the marketing category
ts
new ConsentiSetup({
  compliance: { type: 'opt-in' },
  profileOverride: {
    preferenceModal: {
      categories: { marketing: null },   // deletes the 'marketing' category entirely
    },
  },
})

rootEl

By default the widget appends a <div id="consenti-root"> to document.body and mounts banners and modals inside it. Use rootEl to mount into your own container instead.

ValueBehaviour
string (CSS selector)Resolved via document.querySelector(). Throws if the element is not found.
HTMLElementUsed directly. Throws if the element is not attached to the document.
omittedCreates #consenti-root and appends it to document.body (default).
Mount into a specific wrapper
ts
// HTML: <div id="consent-wrapper"></div>
new ConsentiSetup({
  compliance: { type: 'opt-in' },
  rootEl: '#consent-wrapper',   // CSS selector
  // rootEl: document.getElementById('consent-wrapper')!,  // or HTMLElement directly
})

darkMode

Enables dark colour tokens for the widget. Setting darkMode: true adds the consenti-root--dark class to the root element, which overrides all CSS custom properties to their dark equivalents.

Dark mode
ts
new ConsentiSetup({
  compliance: { type: 'opt-in' },
  darkMode: true,
})

// Or detect the user's OS preference:
new ConsentiSetup({
  compliance: { type: 'opt-in' },
  darkMode: window.matchMedia('(prefers-color-scheme: dark)').matches,
})

autoInit

By default the widget begins initialising immediately when the constructor runs. Set autoInit: false to prevent this — the widget will not touch the DOM until you explicitly call widget.init().

KeyTypeDefaultDescription
autoInitbooleantrueWhen false, the constructor returns without initialising. Call await widget.init() manually to start the widget. After destroy() you can also call init() again to re-initialise the same instance.
Deferred initialisation
ts
const widget = new ConsentiSetup({
  compliance: { type: 'opt-in' },
  rootEl: '#consent-mount',
  autoInit: false,
})

// Later, once the mount point exists in the DOM:
await widget.init()
widget.onReady(() => console.log('Ready:', widget.hasConsent()))

Minimal configs by use case

Simplest possible — auto-detect compliance

ts
new ConsentiSetup({ })

Explicit GDPR opt-in

ts
new ConsentiSetup({ compliance: { type: 'opt-in' } })

CCPA opt-out (no banner)

ts
new ConsentiSetup({ compliance: { type: 'opt-out' } })

Cross-subdomain consent

ts
new ConsentiSetup({
  compliance: { type: 'opt-in' },
  core: {
    storage: 'cookie',
    cookieDomains: '.example.com', // shared across app.example.com, www.example.com, etc.
  },
})

Authenticated user — cross-device sync

ts
// Server renders the page with the authenticated user's UUID
new ConsentiSetup({
  core: {
    userId: '{{ server_user_id }}',
  },
  api: { enabled: true },
})

GPC strict mode + GTM

ts
new ConsentiSetup({
  compliance: { type: 'opt-in' },
  profileOverride: {
    gpcMode: 'strict', // deny silently, no banner shown
  },
  utils: {
    gtm: { containerId: 'GTM-XXXXXX', adsDataRedaction: true },
  },
})

TypeScript imports

ts
import type {
  ConsentiConfig,          // top-level config object
  ComplianceWidgetConfig,  // compliance section
  TcfWidgetConfig,         // compliance.tcf section
  WidgetCountryResolverFn, // custom geo resolver function type
  CoreConfig,              // core section
  ApiConfig,               // api section
  UtilsConfig,             // utils section
  GtmConfig,               // utils.gtm section
  ThemeConfig,             // core.theme section
} from '@consenti/ui'