Operators handbook: cookie consent
The cookie wall is a per-website switch, not a module. Every tenant has it; it is simply off by default.
When it is on, a visitor who has not chosen yet sees a banner with Reject all, Settings and Accept all. Settings opens a switch per kind of cookie, both off until the visitor turns them on:
- Statistics: how the site is used (the built-in analytics, GA4).
- Marketing: advertising partners measuring ads and showing relevant ones elsewhere (Google Ads through Tag Manager, the Meta Pixel).
The choice is remembered for 12 months. The built-in analytics and the Google and Meta Pixel modules follow it by themselves; a theme reads it for scripts of its own.
What operators set
Websites → General → Cookie wall:
| Setting | Default | What it does |
|---|---|---|
| Show cookie banner | off | The whole feature. Off renders nothing and refuses consent posts. |
| Policy URL | empty | Linked from the banner's expanded panel. /cookies or a full URL. |
| Read more text | empty | Replaces the built-in explanation in the expanded panel. Line breaks are kept. |
| Policy version | 1 |
Bump to re-ask everyone. See below. |
Bumping the version re-asks everyone
A stored consent carries the version it was given under. Raise the number and every existing consent stops counting, so every visitor is asked again on their next page view.
That is the mechanism for a policy change, and it is the only correct one: consent given to the old text is not consent to the new text. Do not bump it for a wording tweak that does not change what you set, since each bump costs you the accumulated accept rate.
Lowering the number is not an undo. It leaves consents given at the higher version stranded, so those visitors are re-prompted as well.
What the banner does and does not control
It does not switch any cookie on or off by itself. It records a choice. What actually stops loading is whatever is gated on that choice: the built-in analytics beacon only mints an id with Statistics consent, the Google module loads GA4 with Statistics and Tag Manager with either kind, the Meta Pixel needs Marketing, and a theme's own scripts follow whatever the theme gates them on.
Functional cookies are always set regardless of the answer: the session, the cart, form-protection tokens, and the consent cookie itself. They are what the site needs to work, they are described as such in the banner text, and Reject does not turn them off.
So the honest summary for a tenant asking what Reject all buys them: no analytics id, no Google or Meta tags, no marketing scripts the theme has gated, and everything functional carries on.
A choice made before the banner had Settings (plain Accept) counts as both kinds: that banner's text asked for analytics and marketing together, so nobody had to be asked again.
The consent record
A choice that allows anything appends a row to a consent log (timestamp, IP, the customer when signed in, and which kinds were allowed) so the tenant can later show consent was given. Reject all writes nothing, since there is nothing to demonstrate.
The cookie itself is _cnst and holds the choice with the policy
version: accepted.<version> (both kinds), statistics.<version> or
marketing.<version> (one), or rejected.<version> (neither). It is
signed and carries no personal data; the signature is what stops a
visitor hand-setting accepted.999 to skip a future policy bump.
Languages
The banner ships its own copy in eleven languages, en, nl, de, fr, es, it, pt, pl, sv/se, no/nb/nn, da/dk, and falls back to English outside that set. The codes after a slash are aliases for the same text.
It follows the request locale, not the website default, so a visitor
on /de/ gets the German banner on a multi-locale site. Nothing needs
configuring for this.
Your own Read more text is a single string and does not translate. On a multi-locale site, either leave it empty and let the built-in explanation follow the locale, or point Policy URL at a page that does translate.
What a theme has to do
Drop the banner once, in the base layout, near the end of <body>:
{{ cms_cookie_banner() }}
It renders nothing when the wall is off or the visitor has already chosen on the current version, so it is safe to call unconditionally in a shared theme.
Then gate anything non-functional on the kind of cookie it sets:
{% if cms_consent_granted('statistics') %}
{# an analytics script #}
{% endif %}
{% if cms_consent_granted('marketing') %}
{# an ads or remarketing script #}
{% endif %}
cms_consent_granted() is false until the visitor has chosen, so a
visitor who has not answered loads nothing. Google and Meta tags need no
gate in the theme: use the Google and
Meta Pixel modules instead of pasting their snippets.
The older cms_consent() still works and returns the raw choice:
'accepted', 'statistics', 'marketing', 'rejected', or null
when no valid choice exists yet. cms_consent() == 'accepted' is only
true when both kinds were allowed, so a script gated that way stays off
for a visitor who allowed one kind. Never test != 'rejected': that
loads the script for everyone who has not answered.
The default banner is deliberately plain, with inline styles so it works in a theme that has not styled it. To reskin it, override the whole file:
@GsidCmsCore/cookie/banner.html.twig → @website/cookie/banner.html.twig
The behaviour is one shared script, assets/cookie-banner.js, which the
shipped banner loads and an override loads the same way instead of
copying it:
<script src="{{ asset('assets/cookie-banner.js') }}" defer></script>
So a fix to the banner's behaviour reaches every theme without a theme
release. The hooks it needs, inside the element with id
cms-cookie-banner:
data-cms-cookie-consenton the form, with the token and the hiddendata-cms-choicefield;data-action="accept","reject"and"save"on the choice buttons, inside or outside the form;data-cms-category="statistics"and"marketing"on the two switches;data-cms-toggleon any button that opens the settings panel (.cms-cookie-banner__details).
After a successful choice the script dispatches a cms:consent event on
document (detail: choice, statistics, marketing). The Google and
Meta tags start on it.
An override from before Settings, with only accept and reject, keeps working as Accept all and Reject all, but its visitors cannot allow one kind on its own.
The template receives settings (the four values above) and t, the
translated strings: intro, read_more, accept, reject,
functional_note, default_body, policy_link, accept_all,
reject_all, settings, save, necessary, always_on,
statistics, statistics_note, marketing, marketing_note.
Endpoint
| Route | Name | What it does |
|---|---|---|
POST /cookie/consent |
cms_cookie_consent |
Record accepted, statistics, marketing or rejected |
CSRF-protected under the token name cms_cookie_consent, and it 404s
when the wall is off on that website, so a stray post cannot create audit
noise. It answers JSON for the banner's fetch() and also tolerates a
plain form post, which is what keeps the banner working with JavaScript
off.