Your Framer Cookie Banner Looks Compliant. It Isn't.

Teal cover. Headline — Your Framer Cookie Banner Looks Compliant. It Isn't. Right side — a Framer cookie banner mockup next to a gtag consent-mode code snippet that shows the required fix. Subheadline — Fix the two defaults before GTM fires.

A step-by-step guide to wiring up GTM, GA4, Hotjar, and Bloomreach Engagement so they only fire after consent, and fixing the bug Framer doesn't tell you about.

The problem

Framer has a built-in cookie banner. It looks compliant, it has a Reject all button, consent categories, and a Privacy Policy link. But out of the box it has two problems that make it non-compliant under GDPR:

Problem 1. Tools load before consent is read.
When a returning visitor lands on your site, Framer loads the stored consent asynchronously. This means GTM fires before the consent state is known, and your analytics tools load before they should.

Problem 2. Reject all contradicts the banner's own UI for necessary cookies.
Framer's banner settings include an option to mark Necessary cookies as always active and not revocable. The banner UI reflects that (Necessary is shown as "Always active"), but the consent signals Framer pushes to GTM do not. When a user clicks Reject all, Framer sends functionality_storage: denied and security_storage: denied anyway. These two signals cover basic site functionality (language preferences, UI state) and security concerns (session integrity, fraud prevention), which is exactly what the banner promised the user would stay on. The UI and the signal disagree, and any tool in GTM that respects these two signals will behave as if Necessary was revoked.

This guide walks through the exact GTM setup that fixes both problems.

What you need

  • Framer site with a custom domain

  • Google Tag Manager (GTM)

  • GA4 connected via GTM

  • Hotjar (optional but used as an example)

  • Bloomreach Engagement / Exponea (optional but used as an example)

The pattern works for any analytics or personalization tool. Hotjar and Bloomreach are just the examples we use here.

Step 1: Enable Framer's cookie banner

In Framer go to Site Settings → Cookie Banner → Enable.

Configure four categories:

Category

Description

Toggle

Necessary

Security and basic functionality

Always active (locked)

Preferences

Personalized content and settings

Off by default

Analytics

Performance tracking

Off by default

Marketing

Ads personalization and tracking

Off by default

Set the banner to show on first visit with Reject all, Customize, and Accept all options.

Step 2: Set consent defaults before GTM loads

In Framer go to Site Settings → Custom Code → Head.

Add this before your GTM snippet:

<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',
  'personalization_storage': 'denied',
  'functionality_storage': 'granted',
  'security_storage': 'granted'
});
</script>
<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',
  'personalization_storage': 'denied',
  'functionality_storage': 'granted',
  'security_storage': 'granted'
});
</script>
<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',
  'personalization_storage': 'denied',
  'functionality_storage': 'granted',
  'security_storage': 'granted'
});
</script>

This runs before GTM loads and sets everything consent-gated to denied by default, while keeping functionality and security granted so basic site operation works before the user interacts with the banner. GTM will respect these defaults and block any tags that require consent. (personalization_storage is the seventh Consent Mode v2 signal, covering video recommendations and similar personalization; include it in defaults for completeness.)

Then add your GTM snippet immediately after:

<!-- Google Tag Manager -->
<script>(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','YOUR-GTM-ID');</script>
<

<!-- Google Tag Manager -->
<script>(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','YOUR-GTM-ID');</script>
<

<!-- Google Tag Manager -->
<script>(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','YOUR-GTM-ID');</script>
<

Replace YOUR-GTM-ID with your GTM container ID.

Step 3: Create the two consent triggers in GTM

You need two triggers that everything runs through:

Trigger 1. DOM Ready

  • Type: DOM Ready

  • Fires on: All Pages

  • Name it: DOM Ready

This handles returning visitors. By the time DOM Ready fires, Framer has already loaded the stored consent into the dataLayer.

Trigger 2: cookie_consent_update

  • Type: Custom Event

  • Event name: cookie_consent_update

  • Fires on: All Custom Events

  • Name it: cookie_consent_update

This handles new visitors. Framer pushes this event when a user makes a consent choice. It also fires for returning visitors when Framer loads stored consent on page load.

Step 4: Fix the Reject all bug

This is the non-obvious part. Framer's banner settings let you mark Necessary as always active and not revocable, and the UI renders that correctly. The consent signals it pushes to GTM do not match that UI: on Reject all, Framer sends functionality_storage: denied and security_storage: denied alongside the tracking signals. The two stories the user sees (banner, stored consent) tell different things to any downstream tool that checks these signals. Until Framer reconciles them, you fix it in GTM.

Create a new tag in GTM:

  • Type: Custom HTML

  • Name: Consent Fix — Functionality + Security Always Granted

  • HTML:

<script>
  function gtag(){window.dataLayer.push(arguments);}
  gtag('consent', 'update', {
    'functionality_storage': 'granted',
    'security_storage': 'granted'
  });
</script>
<script>
  function gtag(){window.dataLayer.push(arguments);}
  gtag('consent', 'update', {
    'functionality_storage': 'granted',
    'security_storage': 'granted'
  });
</script>
<script>
  function gtag(){window.dataLayer.push(arguments);}
  gtag('consent', 'update', {
    'functionality_storage': 'granted',
    'security_storage': 'granted'
  });
</script>
  • Trigger: cookie_consent_update

This tag fires every time consent is updated (including when the user rejects everything) and forces the two necessary signals back to granted. It runs silently after Framer's consent update and aligns the runtime signal with what the banner UI already showed the user.

Without this fix, basic site functionality can break for users who click Reject all, even though your banner promised them Necessary cookies are always active.

Step 5: Configure your analytics and personalization tags

Hotjar and Bloomreach Engagement (or any analytics/personalization tool) should use the dual trigger pattern:

For each tag:

  1. Add DOM Ready as a trigger

  2. Add cookie_consent_update as a second trigger

  3. In the tag settings, enable Additional Consent Checks (under Advanced Settings → Consent Settings) and require analytics_storage

The consent check gates the tag, even if the trigger fires, the tag won't execute unless analytics_storage is granted.

The two triggers handle the two visitor states:

  • DOM Ready: returning visitor, stored consent already loaded by DOM Ready

  • cookie_consent_update: new visitor who just accepted, or returning visitor where Framer pushes consent during page load

GA4 / Google Tag does not need consent gating in the same way. Set it to fire on All Pages. GA4 with Consent Mode v2 handles its own consent signals and sends cookieless pings when consent is denied.

Step 6: Final GTM structure

Your Tags should look like this:

Tag

Triggers

Google Tag (GA4)

All Pages

Hotjar Tracking Code

cookie_consent_update + DOM Ready

Bloomreach / Exponea Tag

cookie_consent_update + DOM Ready

Consent Fix tag

cookie_consent_update

Your Triggers should be:

  • All Pages (Page View)

  • DOM Ready

  • cookie_consent_update (Custom Event)

Step 7: Verify in GTM Preview mode

  1. Open GTM → Preview → enter your site URL

  2. Test as a new visitor (open in incognito):

  • On page load: Hotjar and Bloomreach should show as blocked by consent

  • After clicking Accept all: cookie_consent_update fires → both tags load

  • After clicking Reject all: both tags stay blocked, Consent Fix tag fires and grants functionality + security

  1. Test as a returning visitor who previously accepted:

  • On DOM Ready: both tags should fire immediately without waiting for user interaction

  1. Check the consent timeline in GTM Preview, you should see:

  • Consent Initialization → all denied

  • DOM Ready or cookie_consent_update → consent updated to granted

  • Tags fire after consent is confirmed

Step 8: Update your Privacy Policy

Add a Third-Party Services section to your Privacy Policy listing each tool, what it collects, when it activates (after consent), and a link to the tool's own privacy policy. At minimum cover:

  • Google Analytics 4

  • Google Tag Manager

  • Hotjar

  • Bloomreach Engagement (if used)

  • Cloudflare (infrastructure, no consent required)

Why this matters

The GDPR requirement is not just that you show a banner, it is that tools do not process personal data before consent is given. A banner that looks compliant but loads tracking scripts on page load is not compliant, regardless of how it looks to the user.

The setup in this guide ensures:

  • No tracking fires before consent is known

  • Returning visitors are tracked immediately without unnecessary blocking

  • Reject all genuinely blocks all tracking

  • Necessary functionality works regardless of consent choice

  • GA4 operates in cookieless mode when consent is denied, satisfying Google's Consent Mode v2 requirement

Framer-specific notes

  • Framer pushes cookie_consent_update to the dataLayer whenever consent changes: this is the key event your GTM setup relies on. This behavior was observed in early 2026; verify in DevTools → Application → dataLayer before relying on the event name

  • The functionality_storage / security_storage inconsistency described in Step 4 was present as of early 2026. Test whether it still exists in your version by opening DevTools, clicking Reject all, and inspecting the most recent consent update entry in the dataLayer

  • If Framer aligns the signal with its banner UI in a future update, the Consent Fix tag does no harm: it just redundantly confirms what Framer already set correctly

Related manuals

Once your consent setup is working, the Bloomreach Engagement manuals show how to wire each BR component into it correctly: