Wire / Cookie Banners / Article
┌── POST 08.07 · Cookie Banners · 4 min read

Klaro + Google Consent Mode v2: The Complete Wiring Guide

Klaro has no built-in Consent Mode integration. Here is the exact wiring — default denied before load, a watcher on both consent events, and the returning-visitor bug most guides ship.

TL;DR

Klaro ships no Google Consent Mode integration — you wire it yourself. Set gtag('consent','default', …) to denied before Klaro loads, then push an update from a Klaro watcher. Watch both the consents and saveConsents events, or returning visitors never get their stored consent restored to Google.

Our Klaro overview covers whether self-hosting is the right call. This post covers the part that actually breaks in production: making a self-hosted Klaro speak Google Consent Mode v2 correctly. Everything below is against Klaro 0.7.22.

Step 1: default to denied, before anything else

Consent Mode only works if Google’s tags see a denied state before they fire. That means this block goes in the <head>, above your GTM container and above Klaro:

<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('consent', 'default', {
    ad_storage:             'denied',
    ad_user_data:           'denied',
    ad_personalization:     'denied',
    analytics_storage:      'denied',
    functionality_storage:  'denied',
    personalization_storage:'denied',
    security_storage:       'granted',
    wait_for_update: 500
  });
</script>

The ordering rules here are unforgiving and they are the single most common cause of a “compliant” setup that still leaks. We broke down the execution order in detail in default vs update: timing rules.

Step 2: model Consent Mode signals as Klaro services

Klaro thinks in services; Consent Mode thinks in signals. Don’t try to make one service per signal — map real vendors to services, then derive the signals:

services: [
  {
    name: 'google-analytics',
    title: 'Google Analytics',
    purposes: ['analytics'],
    cookies: [/^_ga/, '_gid'],
  },
  {
    name: 'google-ads',
    title: 'Google Ads',
    purposes: ['advertising'],
    cookies: [/^_gcl/, { pattern: 'IDE', domain: '.doubleclick.net' }],
  },
]

Step 3: the watcher (and the bug almost everyone ships)

Klaro’s consent manager exposes watch(), which takes an object with an update(manager, eventName, data) method. The trap: Klaro emits two different events and most guides only handle one.

  • consents — fires on every individual toggle, and, critically, when loadConsents() restores a stored decision on page load.
  • saveConsents — fires only when the visitor confirms, carrying {changes, consents, type}.

Handle only saveConsents and returning visitors get nothing: their consent is in the cookie, Klaro restores it silently, and Google never hears about it — so analytics stays denied for people who already said yes. Handle only consents and you fire an update on every checkbox flip. Handle both, and de-duplicate:

var lastPushed = null;

function syncConsentMode(consents) {
  var analytics = consents['google-analytics'] ? 'granted' : 'denied';
  var ads       = consents['google-ads']       ? 'granted' : 'denied';
  var payload = {
    analytics_storage:  analytics,
    ad_storage:         ads,
    ad_user_data:       ads,
    ad_personalization: ads
  };
  var fingerprint = JSON.stringify(payload);
  if (fingerprint === lastPushed) return;   // nothing changed
  lastPushed = fingerprint;
  gtag('consent', 'update', payload);
}

klaro.getManager().watch({
  update: function (manager, eventName) {
    if (eventName === 'consents' || eventName === 'saveConsents') {
      syncConsentMode(manager.consents);
    }
  }
});

Note that ad_user_data and ad_personalization are the two v2 signals — omit them and Google treats the setup as v1, which is what silently degrades remarketing audiences.

Step 4: blocking is separate from signalling

This is the conceptual split people miss. Consent Mode tells Google’s tags how to behave. It does not stop anything from loading. Non-Google scripts — Meta Pixel, HubSpot, TikTok — need real blocking, which in Klaro means rewriting their tags:

<script type="text/plain" data-type="text/javascript"
        data-name="meta-pixel">
  /* pixel code */
</script>

A site that sets Consent Mode perfectly and forgets this still fires trackers before consent. That is exactly the gap described in why most cookie audits fail.

Step 5: verify against a real browser

Klaro stores consent in a first-party cookie (klaro by default, 120 days), so testing in a tab that already consented tells you nothing. Test in a fresh profile, and check three things: no _ga/_gcl cookies before the banner is answered, a consent → default entry in the dataLayer before any tag, and an update after clicking accept.

Then confirm it from outside your own browser. CookieInspector’s Consent Mode checker loads the page as a first-time visitor and reports the actual default/update sequence and everything that fired in between — including the returning-visitor case, which is the one this integration usually gets wrong.

Klaro gives you a free, BSD-3-licensed CMP you fully own. It also gives you the job of wiring the signals yourself — do it in this order and it holds up.

C
About the author
Consent Mode HQ
Editorial team at Consent Mode HQ
Read more by author ↗