UI Widget — Plugin System
Plugins let you extend the Consenti widget without modifying its source. Each plugin receives the full typed ConsentiWidgetAPI in initialize() — call any public method, read the resolved profile, access DOM elements, and listen to events.
initialize(), destroy(), and all lifecycle hooks are caught and logged as console.warn — the widget continues normally.Minimal plugin
import { ConsentiPlugin, ConsentiSetup } from '@consenti/ui'
import type { ConsentiWidgetAPI } from '@consenti/ui'
class MyPlugin extends ConsentiPlugin {
initialize(widget: ConsentiWidgetAPI): void {
// widget is the live ConsentiSetup instance — call anything on it
console.log('Widget ready. Has consent:', widget.hasConsent())
}
destroy(): void {
// clean up here — runs when widget.destroy() is called
}
}
const widget = new ConsentiSetup({
core: { profileId: 0 },
plugins: [new MyPlugin()],
})ConsentiWidgetAPI reference
The ConsentiWidgetAPI interface is what your plugin receives.ConsentiSetup implements it, so every method is always available.
State inspection
| Method | Returns | Description |
|---|---|---|
hasConsent() | boolean | True if a valid consent record exists |
getConsent() | ConsentValue | null | The stored consent map keyed by cookie ID |
getConsentDate() | Date | false | Date of the most recent consent submission |
getGTMConsent() | Record<string,string> | null | Consent in Google Consent Mode v2 format |
bannerVisibility() | 'main' | 'gpc' | false | Which banner variant is currently visible |
modalVisibility() | 'preference' | false | Whether the preference modal is open |
getProfile() | ResolvedProfile | null | The resolved profile: cookies, banners, modal config, version |
UI control
| Method | Description |
|---|---|
showBanner(gpc?) | Show the main or GPC banner |
hideBanner() | Hide the banner |
showModal(triggerEl?) | Open the preference modal |
hideModal() | Close the preference modal |
submitConsent(consent) | Submit consent programmatically |
deleteConsent() | Delete the consent record |
reConsent() | Delete consent and re-open the banner |
DOM access
| Method | Returns | Description |
|---|---|---|
getRootElement() | HTMLElement | null | #consenti-root — the container for all widget DOM |
getBannerElement() | HTMLElement | null | #consenti-banner — the banner element (may be hidden) |
getModalElement() | HTMLElement | null | #consenti-modal — the preference modal element |
Lifecycle hooks (optional)
Override these methods to react to consent events. All hooks are optional — only define the ones you need.
| Hook | When it fires |
|---|---|
initialize(widget) | Required. After ConsentiSetup.init() completes. May be async. |
destroy() | Required. When widget.destroy() is called. |
onConsentSubmit(consent) | After consent is saved to storage and POSTed to the API |
onBannerShow() | After the banner becomes visible |
onBannerHide() | After the banner is hidden |
onModalShow() | After the preference modal opens |
onModalHide() | After the preference modal closes |
Example: Analytics plugin
Track consent events with your analytics provider:
import { ConsentiPlugin } from '@consenti/ui'
import type { ConsentiWidgetAPI, ConsentValue } from '@consenti/ui'
class AnalyticsPlugin extends ConsentiPlugin {
private widget!: ConsentiWidgetAPI
initialize(widget: ConsentiWidgetAPI): void {
this.widget = widget
}
destroy(): void {}
onConsentSubmit(consent: ConsentValue): void {
const profile = this.widget.getProfile()
myAnalytics.track('consent_saved', {
profileId: profile?.id,
profileVersion: profile?.version,
...consent,
})
}
onBannerShow(): void {
myAnalytics.track('consent_banner_shown')
}
}Example: DOM extension plugin
Add custom content or styles to the banner without touching Consenti source:
import { ConsentiPlugin } from '@consenti/ui'
import type { ConsentiWidgetAPI } from '@consenti/ui'
class BrandingPlugin extends ConsentiPlugin {
private logoEl: HTMLElement | null = null
initialize(widget: ConsentiWidgetAPI): void {
// Inject custom CSS variable on the root container
const root = widget.getRootElement()
if (root) {
root.style.setProperty('--consenti-color-primary', '#0057b8')
}
}
onBannerShow(): void {
const banner = document.getElementById('consenti-banner')
if (!banner || banner.querySelector('.my-logo')) return
this.logoEl = document.createElement('img')
this.logoEl.src = '/logo.png'
this.logoEl.className = 'my-logo'
this.logoEl.alt = 'My Brand'
this.logoEl.style.cssText = 'height:24px;margin-bottom:8px;display:block;'
banner.prepend(this.logoEl)
}
onBannerHide(): void {
this.logoEl?.remove()
this.logoEl = null
}
destroy(): void {
this.logoEl?.remove()
this.logoEl = null
}
}Example: Profile inspection plugin
Use getProfile() to read cookie definitions and make runtime decisions:
import { ConsentiPlugin } from '@consenti/ui'
import type { ConsentiWidgetAPI } from '@consenti/ui'
class ConditionalLoadPlugin extends ConsentiPlugin {
initialize(widget: ConsentiWidgetAPI): void {
const profile = widget.getProfile()
if (!profile) return
const cookieIds = profile.cookies.map((c) => c.id)
console.log('Profile v' + profile.version + ' manages:', cookieIds)
// Load a script only when analytics_storage is granted
if (widget.getConsent()?.analytics_storage === 'granted') {
this.loadAnalyticsScript()
}
window.addEventListener('consenti:consentSubmitted', () => {
if (widget.getConsent()?.analytics_storage === 'granted') {
this.loadAnalyticsScript()
}
})
}
private loadAnalyticsScript(): void {
if (document.getElementById('analytics-script')) return
const script = document.createElement('script')
script.id = 'analytics-script'
script.src = 'https://analytics.example.com/tracker.js'
document.head.appendChild(script)
}
destroy(): void {}
}Example: Async plugin
initialize() can be async — the widget awaits it before resolving widget.ready:
import { ConsentiPlugin } from '@consenti/ui'
import type { ConsentiWidgetAPI } from '@consenti/ui'
class RemoteConfigPlugin extends ConsentiPlugin {
async initialize(widget: ConsentiWidgetAPI): Promise<void> {
const res = await fetch('/api/consent-config')
const config = await res.json() as { bannerDelay?: number }
if (config.bannerDelay && !widget.hasConsent()) {
await new Promise<void>((resolve) => setTimeout(resolve, config.bannerDelay))
widget.showBanner()
}
}
destroy(): void {}
}Registering plugins
import { ConsentiSetup } from '@consenti/ui'
import { AnalyticsPlugin } from './plugins/analytics'
import { BrandingPlugin } from './plugins/branding'
const widget = new ConsentiSetup({
core: { profileId: 1, regulation: 'gdpr' },
plugins: [
new AnalyticsPlugin(),
new BrandingPlugin(),
],
})initialize() is slow, it delays the rest of widget startup. Keep remote calls in plugins fast, or fire them without await if they are non-blocking.TypeScript tip
Import ConsentiWidgetAPI as a type-only import to keep your plugin decoupled from the ConsentiSetup class:
import { ConsentiPlugin } from '@consenti/ui'
import type { ConsentiWidgetAPI } from '@consenti/ui'
// ^^^^ type-only import — no runtime dependency on ConsentiSetup
class MyPlugin extends ConsentiPlugin {
initialize(widget: ConsentiWidgetAPI) { /* ... */ }
destroy() {}
}