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.
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:
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' },
],
},
},
})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.
| Scenario | API | compliance.type | Resolution |
|---|---|---|---|
| 1A | off | auto-detect | Browser locale + optional geo resolver → compliance group → pre-built profile chunk (dynamic import) |
| 1B | off | fixed (e.g. 'opt-in') | Pre-built profile chunk for that group, loaded via dynamic import |
| 2A | on | auto-detect | GET /resolve-profile returns the best profile URL for the visitor; result cached in sessionStorage (1 h TTL) |
| 2B | on | api.complianceGroup set | Fetches the profile for a specific compliance group — skips auto-resolution |
| Local | any | any | ConsentiProfile registered in code; takes precedence over pre-built profiles when found for the matching compliance type |
| Fallback | any | any | Built-in default GDPR profile (Google Consent Mode v2, four purposes) — always available |
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 availableprofileOverride 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.
// 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.
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
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.
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.
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
| Field | Type | Required | Description |
|---|---|---|---|
defaultLocale | string | Yes | BCP 47 locale used when the requested locale has no translation. E.g. 'en'. |
cookies | Cookie[] | No | List of consent purposes / cookie IDs the widget manages. See Cookie type below. |
translations | Record<string, LocaleTranslations> | No | Banner and modal config keyed by locale. If omitted, the widget falls back to profileOverride or defaults. |
gpcBanner | boolean | No | Derived 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. |
allowedOrigins | string[] | No | Hostnames permitted to submit consent for this profile. Only enforced server-side in API mode. Supports *.example.com wildcards. Empty = allow all. |
dpdpa | DpdpaConfig | No | Required when using the opt-in-dpdpa compliance group. See the DPDPA guide. |
Cookie type
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique purpose identifier. Used as the key in the stored consent map, e.g. 'analytics_storage'. |
mandatory | true | No | Cannot be denied. Toggle rendered as disabled. Value always 'granted'. |
listenGpc | boolean | No | When true and autoHonorGPC is active, this cookie is auto-denied when the browser GPC signal is present. |
expiry | number | No | Consent expiry in days. After this period the banner re-appears. Default: no expiry. |
cpraCategory | 'sale' | 'sharing' | 'sensitive' | No | CPRA classification. Only relevant for the opt-out-strict compliance group. |
LocaleTranslations type
| Field | Type | Required | Description |
|---|---|---|---|
mainBanner | MainBanner | Yes | Banner shown on first visit or when no consent exists. |
gpcBanner | GpcBanner | No | Banner shown instead of mainBanner when GPC is detected. Same shape as MainBanner. Falls back to mainBanner if omitted. |
preferenceModal | PreferenceModal | Yes | Slide-in panel where the user manages individual cookie categories. |
MainBanner / GpcBanner type
| Field | Type | Required | Description |
|---|---|---|---|
position | 'bottom' | 'top' | 'middle' | 'left-bottom' | 'right-bottom' | Yes | Where the banner is anchored on screen. |
htmlText | string | Yes | Body content. HTML is allowed. Sanitise any user-supplied values externally. |
heading | string | No | Heading text. Omit to hide the heading element entirely. |
headingTag | string | No | HTML tag for the heading, e.g. 'h2' (default) or 'p'. |
overlayOpacity | number | No | Full-page overlay opacity, 0–100. 0 disables the overlay. Default: 0. |
showClose | boolean | No | Render a ✕ close button that dismisses the banner without saving consent. |
showLocaleSwitcher | boolean | No | Show a locale switcher in the banner. Only renders when the profile has more than one locale defined. |
buttons | Button[] | Yes | Ordered list of action buttons rendered in the banner footer. |
PreferenceModal type
| Field | Type | Required | Description |
|---|---|---|---|
buttons | Button[] | Yes | Footer action buttons. |
categories | Category[] | Yes | Ordered list of consent categories shown with toggles. |
position | 'center' | 'left' | 'right' | No | Modal panel anchor. Default: 'center'. |
heading | string | No | Modal heading text. |
subheading | string | No | Optional subheading rendered below the heading. |
htmlText | string | No | Optional intro text rendered above the category list. HTML is allowed. |
overlayOpacity | number | No | Modal backdrop opacity, 0–100. Default: 50. |
showClose | boolean | No | Show a ✕ close button in the modal header. |
showLocaleSwitcher | boolean | No | Show a locale switcher in the modal header. Only renders when the profile has more than one locale defined. |
persistent | boolean | No | When true, clicking the overlay does not close the modal. The user must click a footer button. Default: false. |
mobileFullScreenBreakpoint | number | No | Screen 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. |
headingTag | string | No | HTML tag for the modal heading. |
Button type
| Field | Type | Required | Description |
|---|---|---|---|
text | string | Yes | Label rendered on the button. |
style | 'primary' | 'secondary' | 'text' | 'accent' | Yes | Visual 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' | Yes | What 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. |
url | string | When 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
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique identifier. Referenced by Cookie.id associations. |
heading | string | Yes | Category name shown as a toggle label. |
htmlText | string | Yes | Description text beneath the heading. HTML is allowed. |
cookies | string[] | Yes | Cookie IDs that belong to this category. Toggling the category affects all listed IDs simultaneously. |
mandatory | true | No | Toggle is rendered as permanently enabled. Value always 'granted'. |
type | 'consent' | 'legitimate_interest' | No | Legal basis. When legitimate_interest, refusal is stored as 'objected' instead of 'denied'. |
headingTag | string | No | HTML 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
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
// 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.
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
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.
// 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',
},
})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
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'