Consent Mapping Script Examples
The Consent Mapping Script field on Liferay DXP’s third-party Consent Management Platform (CMP) configuration screen accepts a JavaScript snippet that reads a visitor’s consent decision from your CMP and writes it into the CONSENT_STATE cookie Liferay DXP’s backend honors. Liferay DXP ships no default bridge scripts for any CMP vendor. These seven examples give you a starting point to adapt to your own CMP account and category configuration:
See Consent Management Platform (CMP) Integration for a full explanation of the Consent Mapping Script field and the CONSENT_STATE cookie contract.
These script examples are provided on a best effort basis as a reference and starting point and are subject to changes that may happen with any of these third party platforms.
Cookiebot
This script reads Cookiebot’s four consent booleans and writes them into the CONSENT_STATE cookie, leaving CONSENT_TYPE_PERSONALIZATION false because Cookiebot has no personalization category.
(function () {
var COOKIE_NAME = "CONSENT_STATE";
var COOKIE_MAX_AGE_DAYS = 365;
function writeConsentCookie(consent) {
var value = {
CONSENT_TYPE_NECESSARY: !!consent.necessary,
CONSENT_TYPE_FUNCTIONAL: !!consent.preferences,
CONSENT_TYPE_PERFORMANCE: !!consent.statistics,
CONSENT_TYPE_PERSONALIZATION: false
};
var maxAgeSeconds = COOKIE_MAX_AGE_DAYS * 24 * 60 * 60;
document.cookie = COOKIE_NAME + "=" + encodeURIComponent(JSON.stringify(value)) +
"; Max-Age=" + maxAgeSeconds + "; Path=/; SameSite=Lax" +
(location.protocol === "https:" ? "; Secure" : "");
}
writeConsentCookie({necessary: true, preferences: false, statistics: false, marketing: false});
window.addEventListener("CookiebotOnConsentReady", function () {
if (window.Cookiebot && window.Cookiebot.consent) {
writeConsentCookie(window.Cookiebot.consent);
}
});
})();
Cookiebot exposes window.Cookiebot.consent, an object with four read-only booleans: necessary (defaults to true), preferences, statistics, and marketing (each defaults to false). The CookiebotOnConsentReady window event fires once consent is known, whether from a fresh submission or a stored cookie.
The mapping is direct for three categories: CONSENT_TYPE_NECESSARY reads Cookiebot’s necessary boolean, CONSENT_TYPE_FUNCTIONAL reads preferences (Cookiebot’s own definition of “preferences” matches functional cookies conceptually), and CONSENT_TYPE_PERFORMANCE reads statistics (Cookiebot’s “statistics” category is analytics and usage tracking). Cookiebot ships exactly four fixed categories, and none of them is personalization. This script always denies CONSENT_TYPE_PERSONALIZATION.
Liferay DXP renders your CMP’s Script Tag before your Consent Mapping Script, so Cookiebot’s own script tag already runs first on the page. This satisfies Cookiebot’s own installation requirement that any script reading window.Cookiebot load after Cookiebot’s script tag.
CookieScript
This script reads CookieScript’s active categories through CookieScript.instance.currentState() and writes the mapped booleans on load and on every consent event, leaving CONSENT_TYPE_PERSONALIZATION tied to CookieScript’s marketing category rather than a true personalization category.
(function () {
var COOKIE_NAME = "CONSENT_STATE";
var COOKIE_MAX_AGE_DAYS = 180;
function writeConsentCookie(consent) {
var value = encodeURIComponent(JSON.stringify(consent));
var maxAge = COOKIE_MAX_AGE_DAYS * 24 * 60 * 60;
document.cookie = COOKIE_NAME + "=" + value + "; path=/; max-age=" + maxAge + "; SameSite=Lax";
}
writeConsentCookie({
CONSENT_TYPE_NECESSARY: true,
CONSENT_TYPE_FUNCTIONAL: false,
CONSENT_TYPE_PERFORMANCE: false,
CONSENT_TYPE_PERSONALIZATION: false
});
function mapToLiferayConsent(categories) {
categories = categories || [];
return {
CONSENT_TYPE_NECESSARY: categories.indexOf("strict") !== -1,
CONSENT_TYPE_FUNCTIONAL: categories.indexOf("functionality") !== -1,
CONSENT_TYPE_PERFORMANCE: categories.indexOf("performance") !== -1,
CONSENT_TYPE_PERSONALIZATION: categories.indexOf("targeting") !== -1
};
}
function syncFromCookieScript() {
if (typeof CookieScript === "undefined" || !CookieScript.instance) {
return;
}
var state = CookieScript.instance.currentState();
writeConsentCookie(mapToLiferayConsent(state.categories));
}
window.addEventListener("CookieScriptLoaded", syncFromCookieScript);
window.addEventListener("CookieScriptAccept", syncFromCookieScript);
window.addEventListener("CookieScriptAcceptAll", syncFromCookieScript);
window.addEventListener("CookieScriptReject", syncFromCookieScript);
})();
CookieScript.instance is a property, not a function call, and its currentState() method returns {action: 'accept' | 'reject', categories: [...]}. Even when action is reject, categories still includes strict, since that category cannot be rejected. CookieScript fires CookieScriptLoaded once the instance initializes (useful for returning visitors), CookieScriptAccept and CookieScriptAcceptAll on acceptance, and CookieScriptReject on rejection. CookieScriptDecline does not exist in CookieScript’s API; that event name belongs to Cookiebot, a different vendor, and using it here would silently register a listener that never fires.
The mapping is direct for three categories: CONSENT_TYPE_NECESSARY reads strict, CONSENT_TYPE_FUNCTIONAL reads functionality, and CONSENT_TYPE_PERFORMANCE reads performance. CONSENT_TYPE_PERSONALIZATION has no clean match. targeting, which CookieScript labels “Marketing,” is the closest available category, but it represents ad-tracking consent, not content personalization consent. That is a conceptual gap, not merely a naming difference. CookieScript’s unclassified category has no Liferay counterpart, and this script drops it entirely.
Didomi
This script uses placeholder purpose IDs because Didomi ships no fixed category taxonomy at all; you must replace each placeholder with the real purpose ID from your own Didomi project before this script does anything.
(function () {
"use strict";
var COOKIE_NAME = "CONSENT_STATE";
var COOKIE_MAX_AGE_DAYS = 365;
// Replace each placeholder with the real purpose ID from your Didomi
// project (Data Manager > Purposes). Didomi has no default purpose set,
// so there is no universal value to substitute here.
var PURPOSE_MAP = {
CONSENT_TYPE_NECESSARY: null,
CONSENT_TYPE_FUNCTIONAL: "REPLACE_WITH_FUNCTIONAL_PURPOSE_ID",
CONSENT_TYPE_PERFORMANCE: "REPLACE_WITH_PERFORMANCE_OR_ANALYTICS_PURPOSE_ID",
CONSENT_TYPE_PERSONALIZATION: "REPLACE_WITH_PERSONALIZATION_PURPOSE_ID"
};
function writeConsentCookie(state) {
var value = encodeURIComponent(JSON.stringify(state));
var maxAge = COOKIE_MAX_AGE_DAYS * 24 * 60 * 60;
document.cookie = COOKIE_NAME + "=" + value + "; path=/; max-age=" + maxAge + "; SameSite=Lax";
}
function safeDefaultState() {
return {
CONSENT_TYPE_FUNCTIONAL: false,
CONSENT_TYPE_NECESSARY: true,
CONSENT_TYPE_PERFORMANCE: false,
CONSENT_TYPE_PERSONALIZATION: false
};
}
function stateFromDidomiStatus(userStatus) {
var purposes = (userStatus && userStatus.purposes) || {};
function isEnabled(purposeId) {
if (purposeId === null) {
return true;
}
var entry = purposes[purposeId];
return !!(entry && entry.enabled);
}
return {
CONSENT_TYPE_FUNCTIONAL: isEnabled(PURPOSE_MAP.CONSENT_TYPE_FUNCTIONAL),
CONSENT_TYPE_NECESSARY: isEnabled(PURPOSE_MAP.CONSENT_TYPE_NECESSARY),
CONSENT_TYPE_PERFORMANCE: isEnabled(PURPOSE_MAP.CONSENT_TYPE_PERFORMANCE),
CONSENT_TYPE_PERSONALIZATION: isEnabled(PURPOSE_MAP.CONSENT_TYPE_PERSONALIZATION)
};
}
writeConsentCookie(safeDefaultState());
window.didomiEventListeners = window.didomiEventListeners || [];
window.didomiEventListeners.push({
event: "consent.changed",
listener: function () {
if (window.Didomi) {
writeConsentCookie(stateFromDidomiStatus(window.Didomi.getCurrentUserStatus()));
}
}
});
window.didomiOnReady = window.didomiOnReady || [];
window.didomiOnReady.push(function (Didomi) {
writeConsentCookie(stateFromDidomiStatus(Didomi.getCurrentUserStatus()));
});
})();
Didomi is purpose-based, not category-based, and ships no fixed functional, necessary, performance, or personalization taxonomy. Each Didomi project uses either the 11 IAB TCF purposes, which are ad-tech oriented and not a fit for Liferay DXP’s four categories, or fully custom purposes an administrator names in the Didomi console. There is no universal purpose ID this article can hard-code for functional, performance, or personalization consent. You must look up your own project’s purpose IDs in the Didomi console (Data Manager > Purposes) and substitute them into PURPOSE_MAP. Didomi’s own documentation describes custom purposes as deliberately flexible, with no default list to fall back on.
Didomi.getCurrentUserStatus() is the current, non-deprecated method for reading consent; getUserConsentStatusForPurpose() is documented as deprecated. It returns {purposes: {[purposeId]: {enabled: boolean}}, ...}. The consent.changed event fires on any consent change. Didomi’s own documentation recommends registering that listener through the pre-init-safe window.didomiEventListeners queue, as this script does, rather than inside the didomiOnReady callback, to avoid missing early events.
A purpose marked “required” in the Didomi console is Didomi’s closest concept to “necessary” consent: it cannot be refused. Which purpose ID is required is project-specific, not fixed, which is why CONSENT_TYPE_NECESSARY maps to null in PURPOSE_MAP and always evaluates to true rather than pointing at a placeholder purpose ID.
OneTrust
This script reads OneTrust’s active category IDs and maps three of your tenant’s four default categories cleanly, treating personalization as a deliberate compliance decision rather than a default.
(function () {
"use strict";
var ONETRUST_CATEGORY_IDS = {
necessary: "C0001",
performance: "C0002",
functional: "C0003",
personalizationProxy: "C0004" // Targeting: approximate only. Confirm before use.
};
function writeConsentStateCookie(state) {
var value = encodeURIComponent(JSON.stringify(state));
var attributes = "; path=/; max-age=" + (365 * 24 * 60 * 60) + "; SameSite=Lax";
if (window.location.protocol === "https:") {
attributes += "; Secure";
}
document.cookie = "CONSENT_STATE=" + value + attributes;
}
writeConsentStateCookie({
CONSENT_TYPE_FUNCTIONAL: false,
CONSENT_TYPE_NECESSARY: true,
CONSENT_TYPE_PERFORMANCE: false,
CONSENT_TYPE_PERSONALIZATION: false
});
function mapAndWrite(activeGroupIds) {
var isActive = function (id) {
return activeGroupIds.indexOf(id) !== -1;
};
writeConsentStateCookie({
CONSENT_TYPE_FUNCTIONAL: isActive(ONETRUST_CATEGORY_IDS.functional),
CONSENT_TYPE_NECESSARY: isActive(ONETRUST_CATEGORY_IDS.necessary),
CONSENT_TYPE_PERFORMANCE: isActive(ONETRUST_CATEGORY_IDS.performance),
CONSENT_TYPE_PERSONALIZATION: isActive(ONETRUST_CATEGORY_IDS.personalizationProxy)
});
}
window.addEventListener("OneTrustGroupsUpdated", function (event) {
var activeGroupIds = (event && event.detail) ||
(typeof window.OnetrustActiveGroups === "string"
? window.OnetrustActiveGroups.split(",").filter(Boolean)
: []);
mapAndWrite(activeGroupIds);
});
})();
OneTrust exposes window.OnetrustActiveGroups, a comma-delimited string of active category IDs, and fires the OneTrustGroupsUpdated window event, whose event.detail is an array of active category IDs, both on initial load and on every consent change.
Category IDs are tenant-specific and configurable in each customer’s OneTrust tenant under Categorization > Categories; they are not a fixed platform constant. The common default template uses C0001 for Strictly Necessary, C0002 for Performance, C0003 for Functional, and C0004 for Targeting.
Confirm your own tenant’s category IDs in Categorization > Categories before adapting this script. The default template’s IDs are common, not universal.
Three categories map cleanly: CONSENT_TYPE_NECESSARY reads Strictly Necessary, CONSENT_TYPE_PERFORMANCE reads Performance, and CONSENT_TYPE_FUNCTIONAL reads Functional. OneTrust has no equivalent for personalization. Targeting, OneTrust’s ad-tracking category, is the nearest available category, but it is not the same as on-site personalization. Mapping Targeting to CONSENT_TYPE_PERSONALIZATION, as this script’s personalizationProxy entry does, is a deliberate compliance decision you make for your own site, not a default you should enable without review.
Osano
This script installs Osano’s documented pre-load queue shim so it can register consent handlers before Osano’s own script tag loads, then maps five of Osano’s six categories, leaving CONSENT_TYPE_FUNCTIONAL false because Osano has no functional category.
(function () {
"use strict";
var COOKIE_NAME = "CONSENT_STATE";
var SAFE_DEFAULT = {
CONSENT_TYPE_FUNCTIONAL: false,
CONSENT_TYPE_NECESSARY: true,
CONSENT_TYPE_PERFORMANCE: false,
CONSENT_TYPE_PERSONALIZATION: false
};
function writeConsentCookie(consentState) {
var value = encodeURIComponent(JSON.stringify(consentState));
document.cookie = COOKIE_NAME + "=" + value + "; Path=/; SameSite=Lax; Max-Age=31536000";
}
writeConsentCookie(SAFE_DEFAULT);
(function (w, o, d) {
w[o] = w[o] || function () { w[o][d].push(arguments); };
w[o][d] = w[o][d] || [];
})(window, "Osano", "data");
function mapOsanoConsentToLiferay(consent) {
if (!consent) {
return SAFE_DEFAULT;
}
function accepted(category) {
return consent[category] === "ACCEPT";
}
return {
CONSENT_TYPE_FUNCTIONAL: false,
CONSENT_TYPE_NECESSARY: accepted("ESSENTIAL"),
CONSENT_TYPE_PERFORMANCE: accepted("ANALYTICS"),
CONSENT_TYPE_PERSONALIZATION: accepted("PERSONALIZATION")
};
}
function onOsanoConsent(consent) {
writeConsentCookie(mapOsanoConsentToLiferay(consent));
}
window.Osano("onInitialized", onOsanoConsent);
window.Osano("onConsentSaved", onOsanoConsent);
})();
window.Osano.cm.getConsent() returns an object with six categories, each valued ACCEPT or DENY: ANALYTICS, MARKETING, PERSONALIZATION, ESSENTIAL, OPT-OUT, and STORAGE. Osano’s own documentation publishes the pre-load queue shim this script installs (the immediately invoked function that defines window.Osano), so calls like window.Osano('onConsentSaved', callback) work regardless of whether this script or Osano’s own script tag loads first. That shim is the documented way to close the first-load race, not a home-grown polling loop. Internally, the onInitialized and onConsentSaved handlers correspond to Osano’s osano-cm-initialized and osano-cm-consent-saved events; osano-cm-initialized can pass undefined if the visitor has not decided yet, and osano-cm-consent-saved is the authoritative “consent changed” signal. A separate osano-cm-consent-changed event fires on every toggle before the visitor saves and is not authoritative, so this script does not act on it.
Two categories map directly: CONSENT_TYPE_NECESSARY reads ESSENTIAL, and CONSENT_TYPE_PERSONALIZATION reads PERSONALIZATION, an exact name match. CONSENT_TYPE_PERFORMANCE reads ANALYTICS by conventional equivalence: Osano has no category literally named “Performance,” but Analytics is the same underlying concept. CONSENT_TYPE_FUNCTIONAL has no Osano equivalent at all. Osano’s default categories are Essential, Analytics, Marketing, and Personalization only, with nothing named or scoped as Functional. This script always denies CONSENT_TYPE_FUNCTIONAL unless your Osano configuration adds a custom category for it. Osano’s MARKETING, OPT-OUT, and STORAGE categories have no Liferay counterpart, and this script drops them.
Termly
This script reads Termly’s consent state and deliberately writes the same value into both CONSENT_TYPE_PERFORMANCE and CONSENT_TYPE_PERSONALIZATION, because Termly’s taxonomy does not distinguish the two.
(function () {
var COOKIE_NAME = "CONSENT_STATE";
var COOKIE_MAX_AGE_DAYS = 365;
function writeConsentCookie(state) {
var value = {
CONSENT_TYPE_FUNCTIONAL: !!state.functional,
CONSENT_TYPE_NECESSARY: !!state.necessary,
CONSENT_TYPE_PERFORMANCE: !!state.performance,
CONSENT_TYPE_PERSONALIZATION: !!state.personalization
};
var expires = new Date();
expires.setTime(expires.getTime() + COOKIE_MAX_AGE_DAYS * 24 * 60 * 60 * 1000);
document.cookie = COOKIE_NAME + "=" + encodeURIComponent(JSON.stringify(value)) +
"; expires=" + expires.toUTCString() + "; path=/; SameSite=Lax";
}
writeConsentCookie({necessary: true, functional: false, performance: false, personalization: false});
function mapTermlyState(consentState) {
consentState = consentState || {};
return {
necessary: !!consentState.essential,
functional: !!consentState.performance,
performance: !!consentState.analytics,
personalization: !!consentState.analytics
};
}
function handleTermlyConsent(data) {
if (data && data.consentState) {
writeConsentCookie(mapTermlyState(data.consentState));
}
}
window.onTermlyLoaded = function () {
if (window.Termly && typeof window.Termly.on === "function") {
if (typeof window.Termly.getConsentState === "function") {
var current = window.Termly.getConsentState();
if (current) {
writeConsentCookie(mapTermlyState(current));
}
}
window.Termly.on("consent", handleTermlyConsent);
}
};
})();
Termly.getConsentState() is available only after Termly’s embed script fully loads. Termly.on('consent', callback) fires with data.consentState on load and on every consent change. Termly defines six categories: essential, performance (labeled “Performance and Functionality”), analytics (labeled “Analytics and Customization”), advertising, social_networking, and unclassified.
CONSENT_TYPE_NECESSARY reads essential cleanly. CONSENT_TYPE_FUNCTIONAL reads performance, because Termly’s own label folds “Functionality” into that category. CONSENT_TYPE_PERFORMANCE and CONSENT_TYPE_PERSONALIZATION both read analytics, since Termly’s analytics category explicitly covers usage measurement and “customization,” with no separate category for either concept. Under this mapping, performance and personalization are always identical for a Termly-integrated site, because Termly’s taxonomy does not distinguish them.
Termly’s own standard installation snippet requires the embed <script> tag to include onload="onTermlyLoaded()". Without that attribute, this script’s listener never registers.
Usercentrics
This script targets Usercentrics’s current (v3) API, checking for window.__ucCmp and mapping its two default categories that correspond to Liferay’s, while leaving performance and personalization false since v3 has no default category for either.
(function () {
"use strict";
var COOKIE_NAME = "CONSENT_STATE";
var CATEGORY_ID_MAP = {
CONSENT_TYPE_NECESSARY: "essential",
CONSENT_TYPE_FUNCTIONAL: "functional",
CONSENT_TYPE_PERFORMANCE: null,
CONSENT_TYPE_PERSONALIZATION: null
};
var SAFE_DEFAULT = {
CONSENT_TYPE_FUNCTIONAL: false,
CONSENT_TYPE_NECESSARY: true,
CONSENT_TYPE_PERFORMANCE: false,
CONSENT_TYPE_PERSONALIZATION: false
};
function writeConsentCookie(consent) {
var value = encodeURIComponent(JSON.stringify(consent));
var attrs = "path=/; max-age=" + (365 * 24 * 60 * 60) + "; SameSite=Lax";
if (window.location.protocol === "https:") {
attrs += "; Secure";
}
document.cookie = COOKIE_NAME + "=" + value + "; " + attrs;
}
writeConsentCookie(SAFE_DEFAULT);
function deriveConsent(categories) {
var consent = {};
for (var key in SAFE_DEFAULT) {
var categoryId = CATEGORY_ID_MAP[key];
var category = categoryId ? categories[categoryId] : null;
consent[key] = category ? category.state === "ALL_ACCEPTED" : SAFE_DEFAULT[key];
}
return consent;
}
window.addEventListener("UC_UI_INITIALIZED", function () {
if (window.__ucCmp && typeof window.__ucCmp.getConsentDetails === "function") {
window.__ucCmp.getConsentDetails().then(function (details) {
writeConsentCookie(deriveConsent(details.categories));
}).catch(function () {});
}
});
window.addEventListener("UC_CONSENT", function (event) {
if (event.detail && event.detail.categories) {
writeConsentCookie(deriveConsent(event.detail.categories));
}
});
})();
Usercentrics has two current API generations. The earlier version (CMP v2) uses window.UC_UI; the current version (v3) uses window.__ucCmp, whose methods, such as getConsentDetails(), return Promises. This example targets the current v3 API.
Check whether your deployment exposes window.__ucCmp or window.UC_UI before adapting this script. The two API generations are not interchangeable.
Usercentrics v3 defaults to exactly three categories: essential, functional, and marketing. There is no default performance or personalization category. Usercentrics fires UC_UI_INITIALIZED once the CMP finishes initializing, and UC_CONSENT on every consent change, with event.detail carrying the full consent-details object.
CONSENT_TYPE_NECESSARY reads essential cleanly, and CONSENT_TYPE_FUNCTIONAL reads functional cleanly. CONSENT_TYPE_PERFORMANCE and CONSENT_TYPE_PERSONALIZATION have no default Usercentrics category to read. If your Usercentrics account has no distinct performance or personalization category, you must decide deliberately whether to leave these false or fold them into an existing category, such as marketing. This script leaves them false by default rather than guessing.