Consenti

UI Widget — 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.

Minimal config to get started

This is all you need for a fully working consent banner. Everything below is optional.

main.ts
ts
import { ConsentiSetup } from '@consenti/ui'

// Auto-detects compliance from browser locale (GDPR, CCPA, etc.)
const widget = new ConsentiSetup({})

// Or pin a specific compliance group:
// new ConsentiSetup({ compliance: { type: 'opt-in' } })   // GDPR
// new ConsentiSetup({ compliance: { type: 'opt-out' } })  // CCPA
ℹ️The rest of this page is the complete configuration reference. You don't need to read it all now — come back when you need to change a specific option like theming, locale, GPC handling, or GTM integration.

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: {
      minAge: 18,
      defaultDeny: true,
    },
  },

  // ── Core behaviour (optional) ───────────────────────────────────────────────
  core: {
    tenantId: 'my-site',         // logical identifier for this installation
    locale: 'en',                // BCP 47; falls back to language prefix, then 'en'
    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: false,

  // ── 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. See the Compliance groups table below.
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. When set, the widget shows an age-verification step before the consent banner. minAge defaults to 18.defaultDeny pre-denies all non-mandatory cookies when the user is under-age.

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.
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. When a containerId is provided, Consenti pushes consent state to the dataLayer on every consent update.

KeyTypeDefaultDescription
containerIdstringundefinedGTM container ID, e.g. 'GTM-XXXXXX'. Required to enable any dataLayer pushes. Omit to skip GTM entirely.
dataLayerstring'dataLayer'Name of the dataLayer array on window. Override only when your site uses a custom variable name (rare).
eventsstring[][]Allowlist of Consenti event names to push. An empty array (default) means push all consent events. Pass specific names to filter, e.g. ['consentSubmitted'].
urlPassthroughbooleanfalsePushes { url_passthrough: true }to the dataLayer alongside consent events. Enables Google Consent Mode v2 "cookieless pings" — Google can model conversions even when ad_storage is denied. Requires gtag.js on the page.
adsDataRedactionbooleanfalsePushes ads_data_redaction: true when ad_storage is denied, causing Google to redact identifying fields from ad pings. Requires gtag.js on the page.
GTM + Consent Mode v2
ts
new ConsentiSetup({
  compliance: { type: 'opt-in' },
  utils: {
    gtm: {
      containerId: 'GTM-XXXXXX',
      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 Profiles guide 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: [
        { text: 'Accept',  style: 'primary',   action: 'custom', cookies: '*' },
        { text: 'Decline', style: 'secondary', action: 'custom', cookies: '!' },
      ],
    },
  },
})

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
  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'