Google Consent Mode V2 with Drupal + Klaro
Posting this mostly as documentation for myself, because every time I have to think about this it hurts my brains.
Anyway..
What is Google Consent Mode V2?
Google Consent Mode V2 is an enhanced version of Google's privacy framework that allows websites to adjust how Google tags behave based on user consent choices. It uses these consent states (two of which are new in V2):
- ad_storage - Controls storage of advertising cookies
- analytics_storage - Controls storage of analytics cookies
- ad_user_data - Controls sending user data to Google for advertising (NEW)
- ad_personalization - Controls personalized advertising (NEW)
- functionality_storage - Controls functional cookie storage
- personalization_storage - Controls personalization cookie storage
- security_storage - Controls security-related storage
Fun fact: the gcd= parameter you’ll see in network requests is just a Base64-ish bitmask of all seven consent flags—hand-crafted obfuscation straight from Mountain View.
Integrating with Drupal + Klaro
The Klaro module for Drupal provides excellent cookie consent management, but integrating it with Google Consent Mode V2 required some additional work. The main challenge was that the existing Klaro module (version 3.0.7) lacked support for the new callback system needed for proper consent mode integration.
Here are the things I did to make it work with a typical setup using GTM + Analytics + Ads
Step 1: Upgrading Klaro with New Callbacks
The first step was applying a patch from Drupal issue #3484827 to add support for onInit, onAccept, and onDecline callbacks. We used the patch from Merge Request #79.
Step 2: Configuring Google Tag Manager Service
We configured the GTM service (config/sync/klaro.klaro_app.gtm.yml) to handle the consent initialization and GTM container loading. This is basically what is described in https://klaro.org/docs/tutorials/google_tag_manager
on_init: |
window.dataLayer = window.dataLayer || [];
window.gtag = function(){dataLayer.push(arguments)}
gtag('consent', 'default', {
'ad_storage': 'denied',
'analytics_storage': 'denied',
'ad_user_data': 'denied',
'ad_personalization': 'denied',
'functionality_storage': 'denied',
'personalization_storage': 'denied',
'security_storage': 'granted'
});
on_accept: |
// Load GTM container
if (!document.querySelector('script[src*="googletagmanager.com/gtm.js"]')) {
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-TK7LMN4');
}
// Notify GTM about all services that were accepted. These are the custom triggers that
// will be used in Step #5.
for(let k of Object.keys(opts.consents)){
if (opts.consents[k]){
let eventName = 'klaro-'+k+'-accepted';
dataLayer.push({'event': eventName});
}
}Step 3: Configuring Google Analytics Service
The GA service (config/sync/klaro.klaro_app.ga.yml) handles analytics consent updates:
on_init: |
window.gtag = function(){dataLayer.push(arguments)}
on_accept: |
gtag('consent', 'update', {'analytics_storage': 'granted'});
on_decline: |
gtag('consent', 'update', {'analytics_storage': 'denied'});Step 4: Configuring Google Ads Service
For advertising consent, we configured the Google Ads service (config/sync/klaro.klaro_app.google_ads.yml):
on_accept: |
gtag('consent', 'update', {
'ad_storage': 'granted',
'ad_user_data': 'granted',
'ad_personalization': 'granted'
});
on_decline: |
gtag('consent', 'update', {
'ad_storage': 'denied',
'ad_user_data': 'denied',
'ad_personalization': 'denied'
});Step 5: Setting Up GTM Triggers
In Google Tag Manager, we created custom event triggers to listen for Klaro consent events. Here is the example for GA:
- Trigger Type: Custom Event
- Event Names:
klaro-ga-accepted - Use Case: Fire GA4 tags when analytics consent is granted
This trigger should then be attached to the relevant tags.
Our implementation closely follows the official Klaro GTM integration guide, using the opts.consents pattern to iterate through all accepted services and push individual events to dataLayer.
What if something changes?
But wait! What if some marketeer who has access to GTM decides to add a new third party tag like a facebook pixel or whatever? The Drupal consent form won't automatically update so cookies/privacy data might be sent to third parties without consent...
As far as I know, there are two options here:
- Manage consent via GTM
Upside: everything managed in GTM
Downside: no fine grained control per service possible in consent popup, stronger dependency on GTM Have a robust policy/governance for managing third party services
Upside: fine grained control per service, more flexibility
Downside: harder to control, requires discipline and structured process, audits
About the author
- Albert Skibinski is a freelance full-stack developer en co-founder at Jafix.
- I write about web development, long bike rides and food!