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
    ageGate: {                   // see "UI Widget — Age Gate" guide
      enabled: false,
      minimumAge: 13,            // COPPA = 13; GDPR Article 8 = 16
      requireParentalConsent: false,
    },
    tcf: {                       // see "TCF v2.2 Implementation Guide"; cmpId/cmpVersion
      enabled: false,            // must match the backend's tcf config
      cmpId: 0,
      cmpVersion: 1,
    },
  },

  // ── 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
    autoHonorGPC: true,          // false | true | 'strict'
    storage: 'cookie',           // 'cookie' | 'localStorage'
    cookieDomains: '.example.com',
    allowReceipt: true,
    disableCssTemplate: false,
    userId: 'server-assigned-uuid', // authenticated users only
    usePrebuiltProfiles: true,   // load pre-built profiles instead of constructing from config
    cacheResolvedProfiles: true, // cache resolved profile in sessionStorage (1h TTL)
    console: ['error', 'warn'],  // log levels to emit; 'error' | 'warn' | 'info' | 'debug'
    theme: {
      bgColor: '#ffffff',
      textColor: '#1a1a1a',
      primaryColor: '#1565c0',
      primaryTextColor: '#ffffff',
      secondaryColor: '#f0f4f8',
      secondaryTextColor: '#1a3460',
      borderColor: '#e2e8f0',
      accentColor: '#d32f2f',
      accentTextColor: '#ffffff',
      fontFamily: 'system-ui, sans-serif',
      fontSizeBase: '14px',
      fontSizeHeading: '18px',
      fontSizeMultiplier: '1',
      borderRadius: '8px',
      buttonBorderRadius: '4px',
      toggleBgOn: '#1565c0',
      toggleBgOff: '#cccccc',
    },
  },

  // ── 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 consent model 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 that returns the visitor's ISO 3166-1 alpha-2 country code. Used to improve auto-detection when the default locale-based heuristic is not accurate enough. Called once per session.
ageGateAgeGateWidgetConfigundefinedAge gate configuration — { enabled, minimumAge, requireParentalConsent }. When enabled: true, the widget shows a Yes/No age-confirmation prompt before anything else (banner, GPC, CCPA opt-out). Declining denies all non-mandatory cookies immediately; with requireParentalConsent: true it also fires aconsenti:parentalConsentRequiredevent carrying a token for your own out-of-band verification flow. See the "Age Gate" section of the @consenti/ui README for the full behavior.
tcfTcfWidgetConfigundefinedIAB TCF v2.2 client stub configuration — { enabled, cmpId, cmpVersion }, must match the backend's tcf config. When enabled, installswindow.__tcfapi. See the "TCF v2.2 Implementation Guide".

Compliance groups

typeModelCovered regulationsNotes
opt-inOpt-inGDPR, UK GDPR, PIPEDA, POPIA, PDPA-TH, APPI, KVKKBanner 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 consentTCF v2.2, COPPA, general privacy noticesFull-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'

const geoResolver: WidgetCountryResolverFn = async () => {
  const res = await fetch('/api/geo')
  const { country } = await res.json()
  return country // e.g. 'DE', 'US', 'IN'
}

new ConsentiSetup({
  compliance: {
    type: 'opt-in',
    geoDataProvider: geoResolver,
  },
})

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

KeyTypeDefaultDescription
autoHonorGPCboolean | 'strict'falseHow to handle the browser's Global Privacy Control signal.false = ignore. true = pre-deny listenGpc cookies and show the GPC banner variant.'strict' = pre-deny and write consent silently — no banner shown. Individual profiles can also set gpcMode per-profile; this field overrides that when explicitly set.

Profile resolution

KeyTypeDefaultDescription
usePrebuiltProfilesbooleantrueWhen true, Consenti lazy-loads the pre-built profile chunk for the resolved compliance group. Set to false to use only profiles defined via ConsentiProfile or profileOverride.
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.
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.

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
bgColor--consenti-color-bg#ffffffBackground colour for banners and modals.
textColor--consenti-color-text#1a1a1aPrimary body text colour.
primaryColor--consenti-color-primary#1565c0Primary button background and interactive accent colour.
primaryTextColor--consenti-color-primary-text#ffffffText colour rendered on top of primaryColor backgrounds.
secondaryColor--consenti-color-secondary#f0f4f8Secondary / ghost button background.
secondaryTextColor--consenti-color-secondary-text#1a3460Text colour for secondary buttons.
borderColor--consenti-color-border#e2e8f0Borders and dividers.
accentColor--consenti-color-accent#d32f2fBackground for accent-style buttons (destructive actions).
accentTextColor--consenti-color-accent-text#ffffffText colour on top of accentColor.
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.
fontSizeMultiplier--consenti-font-size-mult1Scale multiplier applied to all font sizes. '1.1' = 10% larger throughout.
borderRadius--consenti-border-radius8pxBorder-radius for banner and modal containers.
buttonBorderRadius--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.
Minimal theme override
ts
new ConsentiSetup({
  compliance: { type: 'opt-in' },
  core: {
    theme: {
      primaryColor: '#7c3aed',      // purple accent
      primaryTextColor: '#ffffff',
      borderRadius: '12px',
      buttonBorderRadius: '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.
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: 'secondary', 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' },
  core: {
    autoHonorGPC: '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
  AgeGateWidgetConfig,     // compliance.ageGate 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'