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:
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:
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 GrantedHTML:
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
Open GTM → Preview → enter your site URL
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_updatefires → both tags loadAfter clicking Reject all: both tags stay blocked, Consent Fix tag fires and grants functionality + security
Test as a returning visitor who previously accepted:
On DOM Ready: both tags should fire without waiting for user interaction
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
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_updateto the dataLayer whenever consent changes. This is the key event your GTM setup relies on.The
functionality_storagebug 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.