CMP Integration with Consent Mode
A Consent Management Platform (CMP) does three things: shows users a consent banner, records their choices, and provides a mechanism to communicate those choices to your tags. The last part — communicating choices to GTM — is what CMP integration means in practice.
This article covers the integration patterns for the most common CMPs and how to verify the connection is working correctly.
What a CMP integration actually does
Section titled “What a CMP integration actually does”When a user interacts with a consent banner, the CMP needs to:
- Record the user’s choice (in a cookie, localStorage, or server-side)
- Call
gtag('consent', 'update', {...})with the appropriate consent types - This update propagates through GTM to all tags that check consent state
The CMP integration is responsible for step 2. Without it, GTM never learns about the user’s consent choice and tags either fire without consent (if defaults are granted) or never fire (if defaults are denied).
Integration approach 1: Community Template Gallery
Section titled “Integration approach 1: Community Template Gallery”Most major CMPs publish official GTM templates to the Community Template Gallery. This is the recommended approach for most implementations because:
- Templates handle the consent update automatically
- The integration is maintained by the CMP vendor
- You don’t need to write custom JavaScript
- Updates are versioned and can be pulled when the CMP updates its API
To install a CMP template:
- In GTM, go to Templates → Search Gallery
- Search for your CMP name
- Add the template to your workspace
- Create a tag using the template, configure your CMP account ID
- Set the trigger to Consent Initialization — All Pages
Integration approach 2: CMP callback in custom code
Section titled “Integration approach 2: CMP callback in custom code”For CMPs without a Gallery template, or when you need custom control, call the consent update from your CMP’s JavaScript callback. The general pattern:
// Generic CMP callback pattern// Replace with your CMP's actual callback mechanismmyCMP.onConsentUpdate(function(consentObject) { gtag('consent', 'update', { 'analytics_storage': consentObject.analytics ? 'granted' : 'denied', 'ad_storage': consentObject.advertising ? 'granted' : 'denied', 'ad_user_data': consentObject.advertising ? 'granted' : 'denied', 'ad_personalization': consentObject.advertising ? 'granted' : 'denied', 'functionality_storage': consentObject.functional ? 'granted' : 'denied', 'personalization_storage': consentObject.personalization ? 'granted' : 'denied' });});Google CMP Partner Program
Section titled “Google CMP Partner Program”Google maintains an official CMP Partner Program that certifies CMPs for Consent Mode v2 compliance. Partners have completed Google’s integration requirements and follow best practices for consent signal passing.
Google CMP Partner Program: cmppartnerprogram.withgoogle.com
Check this list to see which CMPs are officially certified. Certified partners have been tested by Google and follow standardized approaches to consent signal collection and updating. If you are selecting a new CMP, choosing a certified partner reduces integration risk.
Certified CMP partners include (this list grows; check the official program for current status):
- Cookiebot (by Usercentrics)
- OneTrust
- Usercentrics
- CookieYes
- Iubenda
- And many others
CMP-specific integrations
Section titled “CMP-specific integrations”Cookiebot (now Usercentrics Cookiebot) integrates via its official GTM template or via the CookiebotOnAccept / CookiebotOnDecline callback functions.
Via Gallery template:
- Search Gallery for “Cookiebot” (published by Usercentrics)
- Install and create a tag with your Cookiebot Domain Group ID
- Fire on Consent Initialization — All Pages
Via JavaScript callback:
// Add to your page (or a Custom HTML tag on Consent Initialization)window.addEventListener('CookiebotOnLoad', function() { gtag('consent', 'update', { 'analytics_storage': Cookiebot.consent.statistics ? 'granted' : 'denied', 'ad_storage': Cookiebot.consent.marketing ? 'granted' : 'denied', 'ad_user_data': Cookiebot.consent.marketing ? 'granted' : 'denied', 'ad_personalization': Cookiebot.consent.marketing ? 'granted' : 'denied', 'functionality_storage': Cookiebot.consent.preferences ? 'granted' : 'denied' });});
window.addEventListener('CookiebotOnAccept', function() { gtag('consent', 'update', { 'analytics_storage': Cookiebot.consent.statistics ? 'granted' : 'denied', 'ad_storage': Cookiebot.consent.marketing ? 'granted' : 'denied', 'ad_user_data': Cookiebot.consent.marketing ? 'granted' : 'denied', 'ad_personalization': Cookiebot.consent.marketing ? 'granted' : 'denied', 'functionality_storage': Cookiebot.consent.preferences ? 'granted' : 'denied' });});
window.addEventListener('CookiebotOnDecline', function() { gtag('consent', 'update', { 'analytics_storage': 'denied', 'ad_storage': 'denied', 'ad_user_data': 'denied', 'ad_personalization': 'denied', 'functionality_storage': 'denied' });});Cookiebot categories: statistics → analytics_storage, marketing → ad_storage/ad_user_data/ad_personalization, preferences → functionality_storage.
OneTrust provides a GTM integration via its OptanonWrapper callback and the OneTrust.GetDomainData() API. OneTrust also has an official GTM template.
Via OptanonWrapper callback:
// OneTrust fires OptanonWrapper when consent is ready/updatedfunction OptanonWrapper() { // OneTrust consent groups: // C0001 = Strictly Necessary // C0002 = Performance/Analytics // C0003 = Functional // C0004 = Targeting/Advertising
var activeGroups = window.OptanonActiveGroups || '';
gtag('consent', 'update', { 'analytics_storage': activeGroups.includes('C0002') ? 'granted' : 'denied', 'ad_storage': activeGroups.includes('C0004') ? 'granted' : 'denied', 'ad_user_data': activeGroups.includes('C0004') ? 'granted' : 'denied', 'ad_personalization': activeGroups.includes('C0004') ? 'granted' : 'denied', 'functionality_storage': activeGroups.includes('C0003') ? 'granted' : 'denied' });}Note: OptanonActiveGroups is a comma-delimited string of active group IDs. Your group IDs may differ from the C0001-C0004 defaults — verify in your OneTrust configuration.
Usercentrics exposes a UC_UI JavaScript API and fires custom events on consent updates.
// Usercentrics consent update handlerwindow.addEventListener('UC_UI_CMP_EVENT', function(event) { if (event.detail.type === 'CMP_SHOWN' || event.detail.type === 'SAVE_CC' || event.detail.type === 'ACCEPT_ALL' || event.detail.type === 'DENY_ALL') {
var ucData = window.UC_UI ? window.UC_UI.getServicesBaseInfo() : null;
if (!ucData) return;
// Map Usercentrics categories to Consent Mode types // Adjust service names to match your Usercentrics configuration var analyticsConsented = ucData .filter(s => s.categorySlug === 'analytics') .some(s => s.consent.status === true);
var advertisingConsented = ucData .filter(s => s.categorySlug === 'marketing') .some(s => s.consent.status === true);
gtag('consent', 'update', { 'analytics_storage': analyticsConsented ? 'granted' : 'denied', 'ad_storage': advertisingConsented ? 'granted' : 'denied', 'ad_user_data': advertisingConsented ? 'granted' : 'denied', 'ad_personalization': advertisingConsented ? 'granted' : 'denied' }); }});CookieYes fires a custom event cookieyes_consent_update on the document.
document.addEventListener('cookieyes_consent_update', function(evt) { var accepted = evt.detail.accepted || [];
gtag('consent', 'update', { 'analytics_storage': accepted.includes('analytics') ? 'granted' : 'denied', 'ad_storage': accepted.includes('advertisement') ? 'granted' : 'denied', 'ad_user_data': accepted.includes('advertisement') ? 'granted' : 'denied', 'ad_personalization': accepted.includes('advertisement') ? 'granted' : 'denied', 'functionality_storage': accepted.includes('functional') ? 'granted' : 'denied' });});CookieYes also fires cookieyes_banner_load on page load for returning visitors where consent was already recorded:
document.addEventListener('cookieyes_banner_load', function(evt) { // evt.detail contains the stored consent state var accepted = evt.detail.accepted || []; gtag('consent', 'update', { 'analytics_storage': accepted.includes('analytics') ? 'granted' : 'denied', 'ad_storage': accepted.includes('advertisement') ? 'granted' : 'denied', 'ad_user_data': accepted.includes('advertisement') ? 'granted' : 'denied', 'ad_personalization': accepted.includes('advertisement') ? 'granted' : 'denied' });});Iubenda uses its Cookie Solution JavaScript API. Consent state is available via _iub.cs.api.getPreferences().
// Iubenda consent ready callback// Add to Custom HTML tag on Consent Initializationvar _iubCallback = _iubCallback || [];_iubCallback.push(function() { _iub.cs.api.on('consent', function(consent) { var purposes = consent.purposes || {};
gtag('consent', 'update', { // Iubenda purpose IDs (customize for your configuration) // 1 = Necessary, 2 = Basic interactions, 3 = Experience enhancement, // 4 = Measurement, 5 = Targeting 'analytics_storage': purposes[4] ? 'granted' : 'denied', 'ad_storage': purposes[5] ? 'granted' : 'denied', 'ad_user_data': purposes[5] ? 'granted' : 'denied', 'ad_personalization': purposes[5] ? 'granted' : 'denied', 'functionality_storage': purposes[3] ? 'granted' : 'denied' }); });});Handling returning visitors
Section titled “Handling returning visitors”Returning visitors already have a consent choice stored. The CMP should fire its callback immediately on page load to restore the previous consent state — before the wait_for_update timeout expires.
Verify this works:
- Grant consent on a test page
- Close the browser tab
- Reopen the same URL
- Check GTM Preview mode — the consent update should appear near the beginning of the event stream, before your GA4 or Ads tags fire
If returning visitors hit the timeout instead, your CMP is reading its cookie too slowly. Check whether the CMP loads synchronously or asynchronously and whether there is a race condition with the cookie read.
Verifying the integration
Section titled “Verifying the integration”Browser cookie check: After granting consent, open DevTools → Application → Cookies. You should see:
_gaand_ga_XXXXcookies: analytics consent granted_gcl_aucookie: ad_storage granted- Your CMP’s own consent cookie (e.g.,
CookieConsentfor Cookiebot)
GTM Preview mode:
Open the Consent tab on any event after the consent update fires. All required types should show granted.
Network tab:
After granting consent, check for requests to www.google-analytics.com/g/collect. With analytics_storage: denied, the request URL will include &gcs=G100 (consent denied signal). With granted, it will include &gcs=G111.
Console verification:
// Run in browser console to see current consent statewindow.google_tag_data?.ics?.entriesCommon mistakes
Section titled “Common mistakes”CMP fires its callback asynchronously after a script loads. Many CMPs load their script asynchronously, meaning the callback doesn’t fire until after the DOM is ready or a network request completes. If the wait_for_update timeout fires before the CMP callback, tags fire with the default denied state. Increase wait_for_update or switch to the inline consent default approach.
Mismatching category names. CMP consent categories have different names across platforms. Always map CMP categories to Consent Mode types carefully. A typo like analytics vs Analytics can mean consent is never granted.
Not handling the “consent restored from cookie” case. Many implementations only handle onAccept and onDecline but not the page-load restoration case. Returning visitors may never trigger a consent update, leaving tags in their default state.
Testing in the same browser session that already has consent. Always test consent flows in an incognito window or after clearing cookies.