Version 5.2.0
July 21, 2026
Added
- Built-in support for Google Consent Mode v2 (#106). Each cookie group can be mapped to the Google consent signals it grants (
ad_storage,ad_user_data,ad_personalization,analytics_storage,functionality_storage,personalization_storage,security_storage), and a new{{ craft.cookieConsent.consentModeScript() }}Twig variable outputs thegtag('consent', 'default', ...)snippet for the top of your<head>— place it before your gtag.js/GTM snippet (works with SEOmatic's tracking scripts out of the box). Consent updates are pushed automatically when the visitor's stored consent loads and immediately when they submit or change their consent in the banner. The feature is off by default and enabled per site, with settings for the data layer name,wait_for_update,ads_data_redactionandurl_passthrough. Everything rendered server-side is visitor-independent, so pages stay compatible with static caching (schema version 1.8.0). See the README for setup. - New installs map the default cookie groups to the matching consent signals automatically (Statistics →
analytics_storage, Marketing →ad_storage/ad_user_data/ad_personalization, Necessary →functionality_storage/security_storage).
Improved
- The plugin's site routes (the consent
showendpoint) are now registered whenever the plugin is activated for the site, not only while the banner is rendered, so the consent state can be fetched on pages where the banner is hidden.
Version 5.1.1
July 21, 2026
Bugfix
- The banner's CSS and JS assets are now loaded on error pages such as 404s (#111, #55). When Craft renders an error template, Yii's error handler clears all assets registered on the view, so the bundles the plugin registered during initialization never reached the page (while the banner markup itself, rendered via a template hook, survived). The plugin now registers its asset bundles when the page template renders — after the error handler's reset.
Version 5.1.0
July 21, 2026
Added
- The plugin's templates are now registered as site templates, so the consent form can be embedded on a specific page (e.g. a privacy policy page) with
{% include 'cookie-consent/banner' %}(#57). See the README for details. This also means you can override the plugin templates from your site'stemplates/cookie-consent/folder. - The consent cookie's lifetime is now configurable per site via the new "Consent cookie expiry (days)" setting — previously hardcoded to one year (#121). Defaults to 365 days (schema version 1.7.0).
Improved
- The banner script now dispatches a
ccc-loadedevent onwindowwhenever the consent state has been loaded or updated, with the consent state as the eventdetail(#118). This lets site scripts react to consent changes (e.g. loading GTM after acceptance) without pollingwindow.cccor reloading the page — see the README for an example. - CP save/delete/invalidate actions now use Craft's
asSuccess()/asFailure()/asModelSuccess()/asModelFailure()response helpers. Saving site settings now redirects after success (preventing form re-submission on refresh) and stays on the selected site, and a failed cookie group deletion now shows an error instead of silently redirecting. - The front-end banner bootstrapping (settings lookup, asset loading, template hook) is now deferred with
Craft::$app->onInit()as recommended by the Craft plugin guide, instead of querying the database during plugin initialization. - Added a database index on the consent table (
site_id,dateUpdated), speeding up the CP consent list and the retention command on large tables (schema version 1.6.0). - The banner form's refresh settings are now rendered as proper quoted
data-refresh/data-refreshtimeattributes via the newcraft.cookieConsent.refreshEnabledandcraft.cookieConsent.refreshTimevariables.getRefreshDataAttr()(which returned a raw unescaped HTML string) is deprecated but still works for custom templates. - Modernized the banner script: removed IE8-era
attachEventfallbacks and the hand-rolled form serializer, replacedXMLHttpRequestwithfetch(), merged the duplicated save/accept-all submit paths, and errors are now reported withconsole.error. Loading the JS asset without the banner template no longer throws. The script now requires a browser withfetchsupport (all browsers since ~2017). - The
cookie-consent/retention/clearconsole command now deletes consents with a single query instead of loading every record into memory, returns proper exit codes, writes errors to stderr, and aborts with an error when--sidor--handledoesn't match a site. Previously an unknown site was silently ignored and consents were cleared for all sites. - The consent submission endpoint now requires a POST request, validates
site_id, returns a JSON object (success,uid,consent) instead of a baretrue, and setsSameSite=Laxon the consent cookie. Submitting for a site without settings now returns a 400 response instead of causing a PHP error.
Internal
- Documentation: added README sections on working with static caching / Blitz (#115, #120), Google Consent Mode v2 (#106), and embedding the consent form on a specific page.
- Added recommended composer.json metadata: plugin description, developer name/URL/email and issue tracker/docs support links.
- Site-permission and required-group lookups now use strict
in_array()comparisons per the Craft coding guidelines, andgetConsent()requires a string slug. - PHPStan level raised from 0 to 4 and the codebase passes cleanly; relation magic properties are now documented with
@property-readannotations. - The CP Readme page now resolves the README through the plugin's base path and no longer errors if the file is missing.
- Normalized code style across the plugin: consistent 4-space indentation (tabs removed), braces on all conditionals, and an
.editorconfigadded. - Added parameter and return type declarations throughout (fixing PHP 8.4 implicit-nullable deprecation warnings), replaced the deprecated
SluggableBehavior::className()call and string class names with::classconstants, and corrected copy-pasted docblocks in the record classes.SiteSettings::getLastUpdate()no longer errors when there is nothing to compare. - Removed dead code: a large commented-out block and no-op request calls in the settings controller, an unused variable in the site permission check, leftover TODO stubs and empty asset arrays.
Removed
- Removed the unreachable
settings/retentionCP action: it had no route and referenced a template that never existed. Consent retention is handled by thecookie-consent/retention/clearconsole command (see README).
Security
- "Invalidate Consents" now requires a POST request with a CSRF token and the
cookie-consent:site-settingspermission (plus site edit permission on multisite installs). Previously any logged-in CP user could invalidate all consents via a GET request. The button now asks for confirmation, and an unknown site no longer causes a PHP error.
Bugfix
- If the banner form is submitted natively (JS disabled or not yet loaded — seen on some iOS devices), the consent endpoint now redirects back to the originating page instead of rendering a raw response (#116). The banner script also explicitly requests JSON.
- Validation errors are now displayed when saving a cookie group fails (#96). A strict int/string comparison on
site_idmade the edit page redirect to the Readme after a failed save, discarding the errors. The Name and Description fields are also marked as required in the form. - The "Last updated" date shown by
craft.cookieConsent.updatednow takes cookie group changes into account as intended. The previous code iterated over the relation query object instead of the related records, so group updates never affected the date. - The "Required", "Name", "Description", "Provider" and "Expiry" strings in the banner template were translated with the
sitecategory instead ofcookie-consent, so the plugin's bundled translations (e.g. Norwegian) were never used for them. - Translation overhaul (#82): all remaining hardcoded CP strings are now translatable ("Last invalidated:", the slug-change warning, the consent list pagination), the translation files now cover every string the plugin uses — around 30 missing keys added, including "Accept All" which had no Norwegian translation — and stale keys were removed.
- The IP column in the consent list now renders its status indicator correctly instead of printing escaped HTML.
- The banner no longer errors when a cookie group has no cookies JSON stored.
- An unknown
?site=handle in the CP settings pages now falls back to the primary site instead of causing a PHP error. - Consent list pagination links are now generated with
UrlHelper::cpUrl()and keep the selected site. Previously they were built by string concatenation (broken with a customcpTriggersetup or subdirectory installs) and always jumped back to the primary site on multisite installs. - Using
craft.cookieConsent.*template variables on a site that has no Cookie Consent settings row (or a tampered/non-JSON consent cookie) no longer causes a PHP error. Every variable now falls back to a sensible default when settings are missing. craft.cookieConsent.getConsent()compared stored consent values against the string'on'even though the consent cookie stores booleans; it only returned the right result through PHP's loose comparison rules. It now checks booleans strictly (still accepting'on'from cookies set by very old versions).- Activating the banner made every front-end page uncacheable:
{{ csrfInput() }}in the banner template caused Craft to overwrite the response's cache headers with no-cache headers, and the plugin opened a PHP session (and re-sent the consent cookie) on every request. The CSRF token is now fetched via ajax when consent is submitted, and the consent state is read from the request cookie only.
Breaking
{{ csrfInput() }}was removed from the banner template. If you use a custom template based on the old one, remove{{ csrfInput() }}and adddata-csrf="{{ actionUrl('cookie-consent/consent/csrf') }}"to the form tag to get the same caching fix. Custom templates that keep{{ csrfInput() }}continue to work, but their pages remain uncacheable.
Version 5.0.4
November 7, 2024
Updated
- Dependabot - dependency vulnerability alert (composer.lock). Dependencies in composer.json seems ok, no need for a .lock file so that was removed.
Version 5.0.3
October 22, 2024
Enhancement
- Set secure flag for cookie #101(@white-lukas)
Version 5.0.2
September 9, 2024
Bugfix
- Permission slug mismatch / typo (cookie-consent:cookie-groups:create-new) (#112)
Version 5.0.1
May 2, 2024
Bugfix
- Wrong redirect URL after deletion of cookie group (#109)
Version 5.0.0
April 19, 2024
Deprecated
- before-body-end hook deprecated. Use after-body-start.
Version 4.0.4
September 30, 2022
Enhancement
- Added add, reorder and delete on cookie table (PR #93 from mikewink), fixing #90 and #91.
Version 4.0.3
June 21, 2022
Enhancement
- Switching between sites in multisite installs no longer sends you to the readme-page (#85)
Version 4.0.2
June 21, 2022
Bugfix
- Updated permissions to Craft 4 syntax (#86)
Version 4.0.1
May 27, 2022
Bugfix
- Updated to match new Craft4 multisite cp Routing
Version 4.0.0
May 23, 2022
Updated
- Now supports Craft 4
Version 1.6.1
October 18, 2021
Bugfixes
- Fixed javascript for custom templates where some elements are missing (#56)
Version 1.6.0
September 28, 2021
Added
- Added ability to modify slug of cookie groups (#68)
- Added a javascript object window.ccc that contains all consents for a user (#65)
Enhancement
- Changed the heading from H1 to H3 in the default template (#67)
Bugfixes
- Made refresh_time default to 500ms if left blank (#66)
Version 1.5.0
July 8, 2021
Added
- Added invalidation logic. TODO: Permissions
Enhancement
- Added hooks for after-body-start to reflect the best position for the template rendering. before-body-end will still work as of this version. (#52)
Bugfixes
- Updated the ip column in consents to 39 characters to allow IPv6 (#51)
- Removed duplicate settings field (@mikewink)
- Added missing translations (@mikewink)
Version 1.4.6
February 24, 2021
Bugfix
- Fixed a typo that made install migration fail in some versions of PHP (#63)
Version 1.4.5
January 27, 2021
Bugfix
- Updated the migrations to add primary keys on table creation. (#58)
Version 1.4.4
July 22, 2020
Bugfix
- Fixed a bug in the consent-checker where it used some unset settings before the plugin were activated. (#38)
- Visiting the readme page now retains the current site you're on. (#39)
- Fixed the views to not default to default site when user lacks access to default site. (#42)
Version 1.4.3
July 15, 2020
Bugfix
- Fixed a type in the last migration resulting in failed migration on postgres (#40)
Enhancement
- Updated README.md
Version 1.4.2
June 19, 2020
Enhancement
- Added option to automatically refresh the page when users accept or update their consent.
Version 1.4.1
June 10, 2020
Bugfix
- Fixed a bug in the admin consent view when adding cookie groups after consents have been given (#35)
Version 1.4.0
June 8, 2020
Enhancement
- Added order field for cookie groups (#28)
- Opening expanded cookie banner when revisiting consent (#24)
Version 1.3.2
June 8, 2020
Bugfix
- Fixed hardcoded cpTrigger in consent view pagination (#31)
Version 1.3.1
May 24, 2020
Bugfix
- Updated schema version to automatically apply migration. (#25)
Version 1.3.0
May 23, 2020
Please backup your database before updating. Also backup your database before attempting to use the console command for the fist time.
{info} If you've created custom templates for the banner you might have to update them for this version to work.
Added
- Added option to add a "Accept All" button (#19)
- Added console command for automatically remove old consents (#20)
- Added option to choose cookie name for the plugins consent (#21)
- Added crude pagination for consent overview
Updated
- Changed default setting to have all optional groups toggled off (#19)
Version 1.2.8
February 11, 2020
Bugfix
- Fixed a bug where EditableTables in settings did not output the existing rows. This was due to the values being delivered as a string, not as json. (#16)
Version 1.2.7
January 28, 2020
Bugfix
- Fixed a bug with some elements being null when the cookie tab is not shown after consent
Version 1.2.6
January 28, 2020
Bugfix
- Fixed a bug where the id field was populated with an empty string for new records. This was not accepted by postgres DBs (#13)
Version 1.2.5
January 16, 2020
Bugfix
- Merged in PR #12 from @NRauh to solve issue with consent before the DOM is fully loaded.
Enhancements
- Closing down the consent window before the ajax request to make the function feel more responsive and snappy
Version 1.2.4
October 25, 2019
Fixed
- Merged in PR #9 from @jtrobinson1993 to follow basic accessibility guidelines.
Version 1.2.3
September 30, 2019
Fixed
- Changed license to proprietary in composer.json for packagist
Version 1.2.2
September 24, 2019
Updated
- Consent storage moved from session to cookie
- Composer.json updated to reflect new version
Version 1.2.1
September 24, 2019
Enhancements
- Consent storage moved from session to cookie
Version 1.2.0
August 2, 2019
Bugfix
- Removed alert rendering from site settings (#1)
- CP Links now utilizes cpUrl() to determine link href (#2)
- Popup now utilizes actionUrl() to determine post target (#2)
- CP Links now utilizes cpUrl() to determine link href (#3)
- Cookie accept form now display your current selection after page refresh
- Consents are now ordered by date in the CP list
Version 1.1.0
May 14, 2019
Updated
- Added margins to checkboxes
Added
- Toggle checkboxes on/off for the initial small box
- Toggle a small cookie tab to bring back the consent form after consent is given
Updated
- README now includes an example for SEOmatic
Version 1.0.0.3
May 14, 2019
Bugfix
- The plugin now checks if it's a CP request when rendering the template
Version 1.0.0.2
May 14, 2019
Updated
- License
Version 1.0.0.1
May 14, 2019
Added
- License
Version 1.0.0
May 14, 2019
Added
- Initial Plugin Release