Skip to content

Google Consent Mode V2 with Drupal + Klaro

Albert Skibinski •
Cookies, Spaghetti and exploding brains

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: 

  1.  Manage consent via GTM
    Upside: everything managed in GTM
    Downside: no fine grained control per service possible in consent popup, stronger dependency on GTM
  2. 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

     

Albert Skibinski

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!