Wire / Cookie Banners / Article
┌── POST 10.11 · Cookie Banners · 5 min read

Silktide Consent Manager and Google Consent Mode v2: Add the Default the README Leaves Out

Silktide's open-source Consent Manager sends Consent Mode updates but no default. Set up as the README shows, GA4 fired a page_view with no consent signal and set _ga before any click. The four-line fix, measured.

Illustration: dots already leaving on the left, an empty dashed frame where a piece is missing, and a banner card arriving late from the right.
TL;DR

Silktide’s open-source Consent Manager (MIT, v2.0.1) sends Google Consent Mode updates but never a default. Set it up exactly as the README shows and GA4 sends a page_view with no consent signal at all, and sets _ga cookies, before the visitor has clicked anything — we measured it. After “Reject”, the cookies stay and the next page view goes out the same way. The fix is four lines in <head>: a gtag('consent','default',…) built from the choice Silktide already stored in localStorage. With that in place, a first visit sends gcs=G100, an accept sends G111, and a returning visitor’s first hit carries the right state.

Silktide’s Consent Manager is a small, dependency-free banner: one JavaScript file and one stylesheet, 12.6 KB and 2.5 KB gzipped, served from jsDelivr with published integrity hashes or self-hosted. It is free under the MIT licence, the latest tag is v2.0.1 (July 2026), and it has good keyboard handling. It also has one gap that matters for anyone using Google tags, and the README does not mention it.

What Silktide does with Consent Mode

Each consent category can carry a gtag property naming the Consent Mode signals it controls. When the visitor makes a choice, Silktide calls gtag('consent', 'update', …) with granted or denied for those signals and pushes a stcm_consent_update event to the dataLayer. On later page loads it reads the stored choice and sends the same update again.

That is all. In the v2.0.1 source the only consent commands are three 'update' calls. There is no 'default', no wait_for_update and no region. Google’s Consent Mode documentation is explicit that the default has to run on every page “before any commands that send measurement data (such as config or event)”. Without one, Google’s tags have no consent state to respect until the update arrives — and the update only arrives after a click, or after the banner script has loaded.

What we measured

We served four versions of the same page on 127.0.0.1, each loading gtag.js with a GA4 property and Silktide v2.0.1, and recorded the dataLayer, the cookies and every /g/collect request in Chromium. Each run started from a clean profile.

Setup Before any click After “Reject”, next page Returning visitor who accepted
As in the README (no default) page_view with no gcs; _ga cookies set page_view with no gcs; _ga cookie rewritten update arrives after config
All-denied default + wait_for_update gcs=G100, no cookies G100, no cookies G111, but only because the hit waited for the update
All-denied default, no wait_for_update G100, no cookies G100, no cookies first page_view sent as G100 (denied)
Default rebuilt from Silktide’s stored choice G100, no cookies G100, no cookies G111 on the first hit

gcs is the consent state Google’s tags report: G100 means analytics and ads storage denied, G111 both granted. A hit with no gcs at all is a hit sent with no consent state. Rejecting did not remove the cookies already written — that is a separate problem, covered in why refusing doesn’t delete cookies.

The setup that works

Silktide stores each category as localStorage['stcm.consent.<id>'] with the value "true" or "false", and nothing at all before the first choice. So the default can be rebuilt in <head>, before gtag.js and before config: denied on a first visit, the stored answer afterwards.

<head>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/silktide/consent-manager@v2.0.1/silktide-consent-manager.css">
  <script>
    window.dataLayer = window.dataLayer || [];
    function gtag(){dataLayer.push(arguments);}
    // Silktide stores choices as localStorage['stcm.consent.<id>'] = 'true' | 'false'
    var stcm = function (id) {
      try { return localStorage.getItem('stcm.consent.' + id) === 'true' ? 'granted' : 'denied'; }
      catch (e) { return 'denied'; }
    };
    gtag('consent', 'default', {
      analytics_storage:  stcm('analytics'),
      ad_storage:         stcm('marketing'),
      ad_user_data:       stcm('marketing'),
      ad_personalization: stcm('marketing')
    });
  </script>
  <script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX"></script>
  <script>
    gtag('js', new Date());
    gtag('config', 'G-XXXXXXX');
  </script>
</head>
<body>
  …
  <script src="https://cdn.jsdelivr.net/gh/silktide/consent-manager@v2.0.1/silktide-consent-manager.js"></script>
  <script>
    window.silktideConsentManager.init({
      consentTypes: [
        { id: 'essential', label: 'Essential', description: '…', required: true },
        { id: 'analytics', label: 'Analytics', description: '…', defaultValue: false,
          gtag: 'analytics_storage' },
        { id: 'marketing', label: 'Marketing', description: '…', defaultValue: false,
          gtag: ['ad_storage', 'ad_user_data', 'ad_personalization'] }
      ]
    });
  </script>
</body>

Three details make it work. The gtag function is defined globally in <head>, which is what Silktide checks for before sending its updates. The ids in stcm() must match your consentTypes ids. And if you use the namespace option, the keys become stcm.<namespace>.consent.<id>. Add the SRI integrity attributes Silktide publishes if you load from jsDelivr.

Four other things to know

  • Turn off the pre-ticked analytics toggle. Both the README and Silktide’s install wizard set defaultValue: true on analytics. “Accept all” and “Reject” ignore it, but in the Preferences panel the toggle starts on and “Save” grants it. A pre-ticked box is not consent in the EU (CJEU, Planet49); set it to false, as above.
  • The choice never expires. It lives in localStorage with no lifetime. If you want to ask again after six or twelve months, call resetConsent() yourself — see how long cookie consent should last.
  • It does not block hard-coded tags. Only scripts passed through its scripts option are injected after consent. Anything already in your template runs regardless — the question in open issue #7. Pair it with a Consent Mode default for Google tags and gate everything else.
  • No Global Privacy Control, and dialog semantics are thin. The source never reads navigator.globalPrivacyControl (see honouring GPC in a self-hosted CMP). Focus is trapped inside the banner and preferences, but neither carries role="dialog" or aria-modal, and the preferences close button has no accessible name unless you set preferencesButtonAccessibleLabel.

Silktide’s own install wizard avoids the first problem a different way: its GA4 snippet loads gtag.js only after consent, through the scripts option. That is basic consent mode — no Google tag at all before a choice — and it works; you just get no cookieless pings for modelling. If you follow the README instead, add the default.

For how Silktide compares with Klaro, CookieConsent, tarteaucitron and c15t, see open-source and self-hosted CMP options compared.

The gap here only shows up on the first page load, before anyone clicks. Check what your Google tags send before consent with CookieInspector.

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