How to Make Framer's Cookie Banner GDPR Compliant

A step-by-step guide to wiring up GTM, GA4, Hotjar, and Bloomreach Engagement so they only fire after consent, including a fix for a Framer bug that breaks necessary cookies.

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 breaks necessary cookies. When a user clicks Reject all, Framer sends functionality_storage: denied and security_storage: denied to GTM. These are the consent signals for basic site functionality, things like remembering language preferences and security cookies. The banner UI shows Necessary as "Always active", but the underlying signal says otherwise.

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 the examples used 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',
  'functionality_storage': 'granted',
  'security_storage': 'granted'
});
</script>

This runs before GTM loads and sets everything to denied by default. GTM will respect these defaults and block any tags that require consent.

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>
<

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 part most setups miss. When a user clicks Reject all, Framer sends functionality_storage: denied and security_storage: denied. These should always be granted. They cover basic site functionality, not tracking.

This bug was present as of early 2026. Test whether it still exists in your version by checking the dataLayer in DevTools after clicking Reject all.

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>
  • 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 after Framer's consent update and corrects the values.

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

If Framer fixes this bug in a future update, the Consent Fix tag does no harm. It just confirms what Framer already set correctly.

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's Advanced Settings, enable Additional Consent Checks (BETA) and require analytics_storage

The consent check gates the tag. Even if the trigger fires, the tag will not execute unless analytics_storage is granted.

The two triggers handle the two visitor states:

  • DOM Ready covers returning visitors whose stored consent is already loaded by the time the DOM is ready.

  • cookie_consent_update covers new visitors who just accepted, or returning visitors 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.

This is Google's "Advanced" Consent Mode implementation. It loads the tag before consent and sends cookieless pings to enable conversion modeling. If you prefer the stricter approach, use the same dual trigger pattern as Hotjar (cookie_consent_update + DOM Ready with analytics_storage required) to block GA4 entirely until consent is given. Google calls this "Basic" mode. You lose conversion modeling for non-consenting visitors, but no data reaches Google before consent.

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):

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

  4. After clicking Accept all: cookie_consent_update fires → both tags load

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

  6. Test as a returning visitor who previously accepted:

  7. On DOM Ready: both tags should fire without waiting for user interaction

  8. Check the consent timeline in GTM Preview. You should see:

  9. Consent Initialization → all denied

  10. DOM Ready or cookie_consent_update → consent updated to granted

  11. Tags fire after consent is confirmed

Step 8: Update your Privacy Policy

List each third-party tool in your Privacy Policy: 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, and 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.

This setup also improves page load performance for visitors who decline consent. When tracking scripts are gated behind consent, they never load at all for non-consenting visitors. That means less JavaScript, fewer network requests, and faster rendering.

The setup in this guide ensures:

  • No tracking fires before consent is known

  • Returning visitors are tracked without unnecessary delay

  • Reject all genuinely blocks all tracking

  • Necessary functionality works regardless of consent choice

  • Non-consenting visitors get a faster page load

  • 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.

  • The functionality_storage bug described in Step 4 was present as of early 2026. Test your version by checking the dataLayer in DevTools after clicking Reject all.

  • If Framer fixes this bug, the Consent Fix tag is harmless. It confirms what is already correct.