Consenti

Profiles

A profile defines everything the Consenti widget renders: which cookies / purposes it manages, the banner and modal layout, all button labels and actions, and the text copy for every supported locale. It is the single source of truth for the UI — not the widget config itself.

Minimal — get a working banner fast

You don't need to define a profile to get started. The widget ships 8 pre-built profiles (one per compliance group) that load automatically.

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

// Pre-built GDPR profile loads automatically — no extra code needed.
new ConsentiSetup({ compliance: { type: 'opt-in' } })

To tweak copy, buttons, or position without defining a full profile, use profileOverride:

ts
new ConsentiSetup({
  compliance: { type: 'opt-in' },
  profileOverride: {
    mainBanner: {
      heading: 'We value your privacy',
      htmlText: 'We use cookies to improve your experience.',
      buttons: [
        { text: 'Accept All',     style: 'primary',   action: 'custom', cookies: '*' },
        { text: 'Reject Optional',style: 'secondary', action: 'custom', cookies: '!' },
        { text: 'Customize',      style: 'secondary', action: 'manage' },
      ],
    },
  },
})
ℹ️The rest of this page is the complete profiles reference: resolution order, all ConsentiProfile fields, multi-locale, GPC banner, and more. Only read further when you need full control over the banner structure or multi-locale support.

How profiles are resolved

Every time new ConsentiSetup(config) initialises, it resolves exactly one profile through one of six scenarios. The compliance group (from compliance.type or auto-detected from the browser) determines which pre-built profile is loaded.

ScenarioAPIcompliance.typeResolution
1Aoffauto-detectBrowser locale + optional geo resolver → compliance group → pre-built profile chunk (dynamic import)
1Bofffixed (e.g. 'opt-in')Pre-built profile chunk for that group, loaded via dynamic import
2Aonauto-detectGET /resolve-profile returns the best profile URL for the visitor; result cached in sessionStorage (1 h TTL)
2Bonapi.complianceGroup setFetches the profile for a specific compliance group — skips auto-resolution
LocalanyanyConsentiProfile registered in code; takes precedence over pre-built profiles when found for the matching compliance type
FallbackanyanyBuilt-in default GDPR profile (Google Consent Mode v2, four purposes) — always available
Resolution flow (API enabled)
text
new ConsentiSetup({ api: { enabled: true } })

    ├─ 2A. GET /resolve-profile  ──▶  success → fetch returned profile URL (cached 1h)
    │                                  fail   ↓
    ├─ Local: ConsentiProfile in registry ──▶  found  → use local profile
    │                                           missing ↓
    ├─ 1A. Pre-built profile (auto-detect) ──▶  loaded → use pre-built chunk
    │                                            fail   ↓
    └─ Fallback: built-in default          ──▶  always available
ℹ️After resolution, any properties set in profileOverride are merged on top. See profileOverride below.

Pre-built profiles

Consenti ships pre-built profiles for each compliance group. They are loaded as dynamic import chunks — only the chunk for the resolved compliance group is downloaded. Each pre-built profile covers all four Google Consent Mode v2 purposes plus a mandatory functional bucket.

The fallback built-in profile (used when all resolution steps fail) is an English opt-in GDPR profile — a realistic starting point for any GTM-backed site.

Default profile cookies (fallback)
ts
// Built-in fallback — used when all resolution steps fail
const defaultCookies = [
  { id: 'functionality_storage', mandatory: true },
  { id: 'analytics_storage',     listenGpc: true, expiry: 365 },
  { id: 'ad_storage',            listenGpc: true, expiry: 365 },
  { id: 'ad_user_data',          listenGpc: true, expiry: 365 },
  { id: 'ad_personalization',    listenGpc: true, expiry: 365 },
]

To customise the pre-built profile banner without defining a full profile, use profileOverride.


ConsentiProfile — custom profiles in code

Use ConsentiProfile to define profiles entirely in JavaScript. Each instance is registered in a module-level registry. ConsentiSetup prefers a local profile over the pre-built profile for the same compliance group when one is found.

💡Define the profile before calling new ConsentiSetup() — the registry lookup happens during init. No ID needs to be passed; the profile is matched by compliance group (or used as the default if no group-specific profile is found).

Minimal example

ts
import { ConsentiProfile, ConsentiSetup } from '@consenti/ui'

const profile = new ConsentiProfile({
  defaultLocale: 'en',
  cookies: [
    { id: 'necessary',  mandatory: true,  expiry: 365 },
    { id: 'analytics',  listenGpc: true,  expiry: 365 },
    { id: 'marketing',  listenGpc: true,  expiry: 365 },
  ],
  translations: {
    en: {
      mainBanner: {
        position: 'bottom',
        heading: 'We value your privacy',
        htmlText: 'We use cookies to improve your experience.',
        buttons: [
          { text: 'Accept All',          style: 'primary',    action: 'custom', cookies: '*' },
          { text: 'Reject Optional',          style: 'secondary',  action: 'custom', cookies: '!' },
          { text: 'Customize',  style: 'secondary',  action: 'manage' },
        ],
      },
      preferenceModal: {
        heading: 'Cookie Preferences',
        subheading: 'Choose which cookies you allow.',
        position: 'center',
        showClose: true,
        overlayOpacity: 50,
        buttons: [
          { text: 'Accept All',       style: 'primary',  action: 'custom', cookies: '*' },
          { text: 'Save Preferences', style: 'primary',  action: 'submit' },
          { text: 'Reject Optional',       style: 'text',     action: 'custom', cookies: '!' },
        ],
        categories: [
          {
            id: 'cat-necessary',
            heading: 'Strictly Necessary',
            htmlText: 'Required for the site to function.',
            mandatory: true,
            cookies: ['necessary'],
          },
          {
            id: 'cat-analytics',
            heading: 'Analytics',
            htmlText: 'Helps us understand how visitors use the site.',
            cookies: ['analytics'],
          },
          {
            id: 'cat-marketing',
            heading: 'Marketing',
            htmlText: 'Used to personalise ads and measure campaign performance.',
            cookies: ['marketing'],
          },
        ],
      },
    },
  },
})

new ConsentiSetup({
  compliance: { type: 'opt-in' },
  // ConsentiProfile auto-registered above takes precedence over the pre-built profile
})

Multi-locale profile

Add as many locale keys as needed. The widget resolves locale by trying the exact BCP 47 code, then the language prefix, then defaultLocale.

ts
const profile = new ConsentiProfile({
  defaultLocale: 'en',
  cookies: [
    { id: 'necessary', mandatory: true },
    { id: 'analytics' },
  ],
  translations: {
    en: {
      mainBanner: {
        position: 'bottom',
        htmlText: 'We use cookies.',
        buttons: [
          { text: 'Accept All',  style: 'primary',   action: 'custom', cookies: '*' },
          { text: 'Reject Optional',  style: 'secondary', action: 'custom', cookies: '!' },
        ],
      },
      preferenceModal: {
        heading: 'Cookie Preferences',
        buttons: [{ text: 'Save Preferences', style: 'primary', action: 'submit' }],
        categories: [
          { id: 'cat-analytics', heading: 'Analytics', htmlText: 'Usage stats.', cookies: ['analytics'] },
        ],
      },
    },
    fr: {
      mainBanner: {
        position: 'bottom',
        htmlText: 'Nous utilisons des cookies.',
        buttons: [
          { text: 'Tout accepter',  style: 'primary',   action: 'custom', cookies: '*' },
          { text: 'Tout refuser',   style: 'secondary', action: 'custom', cookies: '!' },
        ],
      },
      preferenceModal: {
        heading: 'Préférences cookies',
        buttons: [{ text: 'Enregistrer', style: 'primary', action: 'submit' }],
        categories: [
          { id: 'cat-analytics', heading: 'Analytiques', htmlText: 'Statistiques.', cookies: ['analytics'] },
        ],
      },
    },
  },
})

new ConsentiSetup({
  compliance: { type: 'opt-in' },
  core: { locale: 'fr' },
})

GPC banner variant

Add a gpcBanner key alongside mainBanner in a locale to show a dedicated banner when the Global Privacy Control signal is detected. Falls back to mainBanner if not provided.

ts
translations: {
  en: {
    mainBanner: { /* ... */ },
    gpcBanner: {
      position: 'bottom',
      heading: 'Privacy signal detected',
      htmlText: "Your browser's GPC signal was detected. Ad cookies have been pre-denied.",
      showClose: false,
      buttons: [
        { text: 'Understood',           style: 'primary',   action: 'custom', cookies: '!' },
        { text: 'Customize',   style: 'secondary', action: 'manage' },
      ],
    },
    preferenceModal: { /* ... */ },
  },
}

ProfileConfig — all options

FieldTypeRequiredDescription
defaultLocalestringYesBCP 47 locale used when the requested locale has no translation. E.g. 'en'.
cookiesCookie[]NoList of consent purposes / cookie IDs the widget manages. See Cookie type below.
translationsRecord<string, LocaleTranslations>NoBanner and modal config keyed by locale. If omitted, the widget falls back to profileOverride or defaults.
gpcBannerbooleanNoDerived flag — do not set manually. It is true when the profile has at least one locale that defines translations[locale].gpcBanner. The actual GPC banner content is configured inside translations[locale].gpcBanner.
allowedOriginsstring[]NoHostnames permitted to submit consent for this profile. Only enforced server-side in API mode. Supports *.example.com wildcards. Empty = allow all.
dpdpaDpdpaConfigNoRequired when using the opt-in-dpdpa compliance group. See the DPDPA guide.
FieldTypeRequiredDescription
idstringYesUnique purpose identifier. Used as the key in the stored consent map, e.g. 'analytics_storage'.
mandatorytrueNoCannot be denied. Toggle rendered as disabled. Value always 'granted'.
listenGpcbooleanNoWhen true and autoHonorGPC is active, this cookie is auto-denied when the browser GPC signal is present.
expirynumberNoConsent expiry in days. After this period the banner re-appears. Default: no expiry.
cpraCategory'sale' | 'sharing' | 'sensitive'NoCPRA classification. Only relevant for the opt-out-strict compliance group.

LocaleTranslations type

FieldTypeRequiredDescription
mainBannerMainBannerYesBanner shown on first visit or when no consent exists.
gpcBannerGpcBannerNoBanner shown instead of mainBanner when GPC is detected. Same shape as MainBanner. Falls back to mainBanner if omitted.
preferenceModalPreferenceModalYesSlide-in panel where the user manages individual cookie categories.

MainBanner / GpcBanner type

FieldTypeRequiredDescription
position'bottom' | 'top' | 'middle' | 'left-bottom' | 'right-bottom'YesWhere the banner is anchored on screen.
htmlTextstringYesBody content. HTML is allowed. Sanitise any user-supplied values externally.
headingstringNoHeading text. Omit to hide the heading element entirely.
headingTagstringNoHTML tag for the heading, e.g. 'h2' (default) or 'p'.
overlayOpacitynumberNoFull-page overlay opacity, 0–100. 0 disables the overlay. Default: 0.
showClosebooleanNoRender a ✕ close button that dismisses the banner without saving consent.
showLocaleSwitcherbooleanNoShow a locale switcher in the banner. Only renders when the profile has more than one locale defined.
buttonsButton[]YesOrdered list of action buttons rendered in the banner footer.

PreferenceModal type

FieldTypeRequiredDescription
buttonsButton[]YesFooter action buttons.
categoriesCategory[]YesOrdered list of consent categories shown with toggles.
position'center' | 'left' | 'right'NoModal panel anchor. Default: 'center'.
headingstringNoModal heading text.
subheadingstringNoOptional subheading rendered below the heading.
htmlTextstringNoOptional intro text rendered above the category list. HTML is allowed.
overlayOpacitynumberNoModal backdrop opacity, 0–100. Default: 50.
showClosebooleanNoShow a ✕ close button in the modal header.
showLocaleSwitcherbooleanNoShow a locale switcher in the modal header. Only renders when the profile has more than one locale defined.
persistentbooleanNoWhen true, clicking the overlay does not close the modal. The user must click a footer button. Default: false.
mobileFullScreenBreakpointnumberNoScreen width in px at or below which the modal expands to fill the entire screen. Default: 576. Set to 0 to disable full-screen on mobile entirely.
headingTagstringNoHTML tag for the modal heading.

Button type

FieldTypeRequiredDescription
textstringYesLabel rendered on the button.
style'primary' | 'secondary' | 'text' | 'accent'YesVisual appearance only — never affects click behaviour.primary = filled accent; secondary = outlined/ghost; text = no border, link-like; accent = destructive red (uses --consenti-accent-color).
action'custom' | 'manage' | 'submit' | 'close' | 'link'YesWhat happens on click. custom = grant/deny the IDs in cookies; manage = open the preference modal; submit = save the current modal toggle state; close = dismiss without saving; link = navigate to url (opens in a new tab).
cookies'*' | '!' | string[]When action: 'custom'Which cookie IDs to affect. '*' = grant all; '!' = deny all; string[] = specific IDs only.
urlstringWhen action: 'link'Target URL for the link button. Opens in a new tab. Typically used for privacy policy or terms links rendered below the banner body.

Category type

FieldTypeRequiredDescription
idstringYesUnique identifier. Referenced by Cookie.id associations.
headingstringYesCategory name shown as a toggle label.
htmlTextstringYesDescription text beneath the heading. HTML is allowed.
cookiesstring[]YesCookie IDs that belong to this category. Toggling the category affects all listed IDs simultaneously.
mandatorytrueNoToggle is rendered as permanently enabled. Value always 'granted'.
type'consent' | 'legitimate_interest'NoLegal basis. When legitimate_interest, refusal is stored as 'objected' instead of 'denied'.
headingTagstringNoHTML tag for the category heading, e.g. 'h3' (default).

profileOverride — runtime overrides

profileOverride lets you change any field of the resolved profile at runtime, without touching the base profile definition. It is applied as a deep merge on top of whatever profile was resolved (default, local, or API).

Common uses: adjusting banner position per-page, swapping button copy for A/B tests, or running the widget with no backend at all by providing the entire profile inline.

ℹ️profileOverride accepts a Partial<ResolvedProfile>. Top-level keys like mainBanner, gpcBanner, and preferenceModal are merged deeply — you only need to supply the keys you want to change. cookies is replaced entirely when provided.

Override banner copy and buttons only

ts
new ConsentiSetup({
  compliance: { type: 'opt-in' },
  profileOverride: {
    mainBanner: {
      heading: 'Cookie notice',
      htmlText: 'We use analytics cookies to improve the site.',
      buttons: [
        { text: 'Accept',  style: 'primary',   action: 'custom', cookies: '*' },
        { text: 'Decline', style: 'secondary',  action: 'custom', cookies: '!' },
      ],
    },
  },
})

Change banner position per page

ts
// On the checkout page, keep the banner out of the way
new ConsentiSetup({
  compliance: { type: 'opt-in' },
  profileOverride: {
    mainBanner: { position: 'right-bottom' },
  },
})

Full inline profile — no backend, no ConsentiProfile

You can provide an entire profile through profileOverride alone. This is the most concise approach for simple frontend-only installations that do not need multi-locale support.

ts
new ConsentiSetup({
  compliance: { type: 'opt-in' },
  profileOverride: {
    cookies: [
      { id: 'necessary',  mandatory: true },
      { id: 'analytics',  listenGpc: true, expiry: 365 },
      { id: 'marketing',  listenGpc: true, expiry: 365 },
    ],
    mainBanner: {
      position: 'bottom',
      heading: 'We value your privacy',
      htmlText: 'We use cookies to improve your experience and personalise ads.',
      buttons: [
        { text: 'Accept All',         style: 'primary',   action: 'custom', cookies: '*' },
        { text: 'Reject Optional',         style: 'secondary', action: 'custom', cookies: '!' },
        { text: 'Customize', style: 'secondary', action: 'manage' },
      ],
    },
    gpcBanner: {
      position: 'bottom',
      heading: 'Privacy signal detected',
      htmlText: "Your browser's GPC signal has been honoured. Ad cookies are pre-denied.",
      buttons: [
        { text: 'Understood',         style: 'primary',   action: 'custom', cookies: '!' },
        { text: 'Customize', style: 'secondary', action: 'manage' },
      ],
    },
    preferenceModal: {
      position: 'center',
      heading: 'Cookie Preferences',
      subheading: 'Choose which cookies you allow us to use.',
      showClose: true,
      overlayOpacity: 50,
      buttons: [
        { text: 'Accept All',       style: 'primary', action: 'custom', cookies: '*' },
        { text: 'Save Preferences', style: 'primary', action: 'submit' },
        { text: 'Reject Optional',       style: 'text',    action: 'custom', cookies: '!' },
      ],
      categories: [
        {
          id: 'cat-necessary',
          heading: 'Strictly Necessary',
          htmlText: 'Required for the site to function. Cannot be disabled.',
          mandatory: true,
          cookies: ['necessary'],
        },
        {
          id: 'cat-analytics',
          heading: 'Analytics',
          htmlText: 'Helps us understand how visitors use the site (e.g. Google Analytics).',
          cookies: ['analytics'],
        },
        {
          id: 'cat-marketing',
          heading: 'Marketing',
          htmlText: 'Used to deliver relevant ads and measure campaign effectiveness.',
          cookies: ['marketing'],
        },
      ],
    },
  },
})

Override modal categories only

ts
new ConsentiSetup({
  compliance: { type: 'opt-in' },
  profileOverride: {
    preferenceModal: {
      categories: [
        {
          id: 'cat-necessary',
          heading: 'Strictly Necessary',
          htmlText: 'Core functionality. Always enabled.',
          mandatory: true,
          cookies: ['necessary'],
        },
        {
          id: 'cat-analytics',
          heading: 'Analytics',
          htmlText: 'Page view and interaction tracking.',
          cookies: ['analytics'],
        },
      ],
    },
  },
})

Using the backend API

When the Consenti API is deployed, profiles are managed in the Admin Dashboard and grouped by compliance type. The widget calls GET /resolve-profile to find the best profile for each visitor automatically.

ts
// Auto-resolve: server picks the right profile for the visitor's locale / country
new ConsentiSetup({
  api: {
    enabled: true,
    baseUrl: 'https://your-site.com', // where @consenti/api is mounted
  },
  core: { locale: 'fr' },
})

// Fixed group: always fetch the GDPR-model profile
new ConsentiSetup({
  api: {
    enabled: true,
    baseUrl: 'https://your-site.com',
    complianceGroup: 'opt-in',
  },
})
💡You can still use profileOverride on top of an API profile — for example to change the banner position on a specific page without creating a new profile variant in the dashboard.

TypeScript imports

ts
import type {
  ProfileConfig,           // passed to new ConsentiProfile(config)
  LocaleTranslations,      // translations[locale] shape
  MainBanner,              // mainBanner / gpcBanner shape
  PreferenceModal,         // preferenceModal shape
  Button,                  // button definition
  ButtonStyle,             // 'primary' | 'secondary' | 'text' | 'accent'
  ButtonAction,            // 'custom' | 'manage' | 'submit' | 'close'
  Category,                // preference modal category
  Cookie,                  // cookie / purpose definition
  ResolvedProfile,         // what profileOverride accepts (Partial<ResolvedProfile>)
  ComplianceWidgetConfig,  // compliance section
  AgeGateWidgetConfig,     // compliance.ageGate section
  WidgetCountryResolverFn, // custom geo resolver function type
  NonEmptyArray,           // [T, ...T[]] — at least one element
} from '@consenti/ui'