# Cookie Consent Banner & GDPR Consent Mode v2

> GDPR cookie consent banner with Google Consent Mode v2, script blocking until consent, a tamper-proof log and presets for GA4 and Meta Pixel.

- **What it is:** GDPR cookie consent banner with Google Consent Mode v2, script blocking until consent, a tamper-proof log and presets for GA4 and Meta Pixel.
- **For:** Website
- **Odoo versions:** 16.0, 17.0, 18.0, 19.0, 20.0 - Community and Enterprise
- **Runs on:** Odoo.sh or Odoo you host yourself - not Odoo Online, which accepts no third-party apps
- **Price:** 89 EUR, one-time, on the Odoo Apps Store
- **Technical name:** bambooforge_consent_pro
- **Manual:** https://docs.bambooforge.dev/bambooforge_consent_pro/
- **Live demo:** https://demo20.bambooforge.dev/odoo/action-bambooforge_consent_pro.action_bf_consent_script
- **Store page:** https://apps.odoo.com/apps/modules/20.0/bambooforge_consent_pro
- **Manual last updated:** 2026-09-21

---
# Cookie Consent Banner & GDPR Consent Mode v2

A complete consent-management platform (CMP) for the Odoo 20 website:
per-category cookie banner with a preferences modal, Google Consent Mode v2
signals, marketing/analytics scripts held back until the visitor agrees, an
audit-ready consent register, EU geo-targeting and one-click presets for the
common trackers.

This page is the complete manual. Follow it top to bottom and you can
install, brand the banner, wire your trackers, prove consent to an auditor
and fix the common issues **without contacting support**.

## Overview

- **Per-category banner** — Essential (always on), Functional, Analytics,
  Marketing. Accept all / Reject non-essential / granular preferences, all
  equally easy, as the GDPR requires.
- **Google Consent Mode v2** — a static `gtag('consent','default',…)`
  emitted at the very top of `<head>` (before any Google tag) and
  `consent update` pushes on every choice, covering `ad_storage`,
  `ad_user_data`, `ad_personalization`, `analytics_storage`,
  `functionality_storage` and `personalization_storage`. Odoo's
  built-in GA4 integration is governed automatically.
- **Script blocking** — register trackers as *Managed Scripts* and they
  are only injected after their category is granted. Visitors who already
  consented get the scripts server-rendered (no flicker, no lost
  pageviews). Hand-placed tags can use the
  `<script type="text/plain" data-bf-consent="analytics">` pattern.
- **Consent register** — every Accept / Reject / Custom / Withdraw choice
  is stored with a salted visitor hash (no personal data), granted
  categories, policy version and country. This is the proof an auditor
  asks for. **One entry per distinct choice**: re-submitting the same
  choice refreshes the existing entry instead of adding a row, while a
  genuine change of mind lands as its own entry (see *The consent
  register* below). Retention is automatic (13 months by default).
- **Geo rules** — ask everyone, or ask only EU/EEA/UK/CH visitors and
  apply implied consent elsewhere. Unknown geolocation is treated as EU
  (the safe default).
- **Presets** — Meta Pixel, TikTok, LinkedIn Insight, Hotjar, Microsoft
  Clarity ship inactive with a placeholder ID: activate, paste your ID,
  done.
- **Re-prompt on policy change** — bump the policy version and every
  visitor is asked again; old cookies become invalid automatically.
- The core single-choice cookies bar is switched off at install so only
  one banner ever shows.

## Requirements

| Item | Detail |
|---|---|
| Odoo | 20.0, Community or Enterprise. Only the `website` app is required. |
| Python / JS | No extra libraries. The banner is framework-free vanilla JS. |
| GeoIP | The *EU-only* mode uses Odoo's GeoIP resolution (`request.geoip`). Without a GeoIP database every visitor is treated as EU — compliant, never under-asking. |

## Getting started

1. Install **BambooForge Consent Pro**. The banner is live immediately
   with sensible defaults; the core cookies bar is switched off.
2. Open **Website ▸ Configuration ▸ Settings ▸ Consent Pro** and pick the
   audience mode (everyone vs EU-only), banner texts and policy version.
3. Move your trackers into **Website ▸ Configuration ▸ Consent Pro ▸
   Managed Scripts** — or activate a preset and paste your pixel ID.
4. Visit the site in a private window: choose, then check **Consent
   Register** — your choice is there.

## Usage

### Managing scripts

Each *Managed Script* holds a category, an optional external `Script
URL` and/or `Inline JavaScript`. Both run only once the visitor grants
that category — injected immediately after the choice (no reload needed)
and server-rendered on later page views.

If you keep a tag hard-coded in a template or through the website editor,
neutralise it like this and Consent Pro will activate it on consent:

```
<script type="text/plain" data-bf-consent="marketing">
    fbq('init', '123456789');
</script>
```

Google tags (GA4, Google Ads) need **no** managed script when Consent
Mode is enabled: keep using Odoo's built-in GA4 field — Google's own
tags read the consent signals and stay dormant until granted.

### The consent register

**Website ▸ Configuration ▸ Consent Pro ▸ Consent Register** lists every
choice: timestamp, salted visitor key, action, granted categories, policy
version, country. Filter and group by action/country/day; export to XLSX
with the standard exporter for an audit bundle. Entries older than the
per-website retention (Settings, default 13 months — CNIL guidance) are
vacuumed automatically.

**One entry per distinct choice.** An entry is identified by *website +
visitor key + policy version + action + granted categories*. When the
same visitor submits **exactly the same choice again** — reloading the
page, clicking *Accept all* twice, a script replaying the request — the
existing entry is refreshed in place and **no row is added**. A **genuine
change of mind** differs in the action or in the categories (accept-all,
then withdraw; analytics on, then analytics off) and is therefore stored
as **its own entry**, so the register still shows what changed and when.

This is deliberate: `/bf_consent/save` is a public endpoint, so
without it anyone could inflate the register into unbounded growth by
replaying one request. Deduplication keeps the register an accurate
picture of the choices actually made — and **retention is unchanged**
(13 months by default). One consequence worth knowing: a refreshed entry
keeps the timestamp of the moment that choice was **first** recorded, so
the register dates the choice, not the last page view that repeated it.

### Changing your cookie policy

Bump **Policy version** in Settings. Existing consent cookies no longer
match, so every visitor is prompted again and new register entries carry
the new version.

### Withdrawing consent

The floating 🍪 badge (and the `window.bfConsent.open()` JS API for
custom footer links) reopens the preferences modal at any time, as the
GDPR requires. Newly revoked categories stop loading from the next page
view; Google tags react immediately via `consent update`.

## Configuration

| Setting (per website) | Effect |
|---|---|
| Consent Pro banner | Master switch. Off = no banner, no scripts blocked, no logging. |
| Audience mode | *Ask every visitor*, or *EU-only* (implied consent elsewhere — scripts load, no banner, badge still available). |
| Google Consent Mode v2 | Emit default + update signals. Leave on if you use any Google tag. |
| Reject button | Shows *Reject non-essential* on the first layer. Keep it on for GDPR compliance. |
| Floating badge | The persistent 🍪 button that reopens preferences. |
| Policy version / Retention | Re-prompt trigger and register retention in months. |
| Banner title / message | First-layer texts, translatable per language. |

Access: website designers manage scripts and read the register;
deleting register entries is reserved to administrators.

## Install

1. Copy `bambooforge_consent_pro` into your addons path and update the app list, or install the ZIP from the Odoo Apps Store.
2. Install **Cookie Consent Banner & GDPR Consent Mode v2**.
3. Open **Managed Scripts** - its menus are Consent Register, Consent Pro.

## Troubleshooting

****Two banners show.****

The core cookies bar was re-enabled. Switch *Cookies Bar* off in
website settings — Consent Pro replaces it.

****My tracker fires before consent.****

It is not registered: move the tag into Managed Scripts or wrap it in
the `text/plain` pattern. Tags pasted into Website ▸ Custom head
code run outside any CMP's control.

****GA4 shows no data after installing.****

That is consent working: Google tags stay dormant until a visitor
grants Analytics. Expect *consented* traffic only — that is the
compliant baseline everywhere in the EU.

****Non-EU visitors still see the banner in EU-only mode.****

Your server has no GeoIP database, so geolocation is unknown and the
safe default applies. Install MaxMind GeoLite2 (see Odoo docs,
`--geoip-city-db`) to enable the distinction.

****I consented twice but the register shows one row.****

That is the deduplication: the same choice (same visitor, policy
version, action and categories) refreshes its entry instead of adding a
row. Change something real — withdraw, or untick a category — and the
new choice appears as its own entry.

****The banner does not show for me.****

You already chose (badge instead), or you are in the website editor
(the banner hides while editing), or your cookie predates a policy
bump on another browser profile.

****Does it slow pages down?****

No measurable impact: the consent-default script is inline and static
(cache-friendly), consented scripts are server-rendered, and the
banner assets ship inside the normal website bundle.

## FAQ

****Is this enough for GDPR/ePrivacy compliance?****

It implements the technical requirements: prior blocking, granular
opt-in, equal reject, withdrawal, proof of consent, re-prompt on
change. Pair it with an accurate cookie policy page — legal texts
remain your responsibility.

****Does it work with Odoo's GA4 integration?****

Yes — that is the flagship case. Consent Mode v2 governs it without
any extra configuration.

****Multi-website?****

Yes: all settings, scripts (optionally shared), register entries and
retention are per-website.

****Does any data leave my server?****

No. No CDN, no external CMP service, no fees per pageview. The
register stores a salted hash, never the raw IP.

****Can visitors change their mind?****

Always — the floating badge or `window.bfConsent.open()` reopens
preferences; revocation is logged like any other choice, as its own
register entry. Only an **identical** re-submission is deduplicated
onto the existing entry.

****Community edition?****

Yes, CE and EE alike.

## Upgrade

1. Back up the database.
2. Replace the module folder, restart, then:
   
   ```
   odoo-bin -d <db> -u bambooforge_consent_pro --stop-after-init
   ```
3. Review the Changelog; bump the policy version if a new release adds
   categories you start using.

## Uninstall

1. **Apps ▸ BambooForge Consent Pro ▸ Uninstall.**
2. Removed: managed scripts, the consent register, banner templates and
   settings fields. Visitors' `bf_consent` cookies simply expire.
3. Re-enable the core *Cookies Bar* in website settings if you still
   want a basic banner afterwards.
4. Remember: without a CMP your trackers fire unconditionally again —
   check your compliance posture before removing.

## Changelog

### 20.0.1.2.2

- Built for Odoo 20: record rules and access rights rewritten onto Odoo 20's single ir.access model, the screens rebuilt on Owl 3, and every icon moved to the icon set Odoo 20 ships.

### 20.0.1.0.1 (2026-08-15)

- **The consent register keeps one entry per distinct choice.**
  Re-submitting the *same* choice (same visitor, policy version, action
  and granted categories) now refreshes the existing entry instead of
  adding a row; a genuine change of mind (different action or different
  categories — accept-all then withdraw, a category toggled) is still
  stored as its own entry, so the audit trail of what changed and when
  is preserved.
- Rationale: `/bf_consent/save` is a public endpoint, and this keeps
  the register an accurate record instead of something a replayed
  request can grow without bound. Retention (13 months by default) is
  unchanged.

### 20.0.1.0.0 (2026-08-14)

- Initial release: per-category banner + preferences modal, Google
  Consent Mode v2 (default in head + updates), managed scripts with
  prior blocking and server-side rendering after consent,
  `text/plain` unblock pattern, audit-ready consent register with
  salted visitor keys and automatic retention, EU/EEA/UK/CH geo mode,
  floating re-consent badge and JS API, presets for Meta Pixel, TikTok,
  LinkedIn, Hotjar and Clarity, policy-version re-prompt, multi-website
  support, browser-tested (frontend tour) with a full HTTP test suite.

## How we make sure it works

Before every release, **19 automated checks** run against this app on Odoo 16, 17, 18, 19 and 20.
Among them:

- Repeat post does not inflate register.
- Disabled website renders nothing.
- Rejected visitor never gets script.
- Core ga back when consent disabled.
- Core ga is blocked until consent.

The complete list is in the source you receive.
