Search by

tecnostack / module-cookie-consent

pro4soft

Cookie banner and privacy preference centre for Magento 2 with Google Consent Mode v2, script/iframe/pixel blocking, a GTM dataLayer stream and a server-side consent log. Theme independent (Luma, Hyva, custom themes), RTL ready.

Package info

github.com/Sayed-Kerkesh/magento2-cookie-consent

Type:magento2-module

pkg:composer/tecnostack/module-cookie-consent

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

1.0.1 2026-10-26 08:01 UTC

This package is auto-updated.

Last update: 2026-10-26 08:04:15 UTC


README

Cookie Consent for Magento 2: the privacy preference centre on the Luma theme in English, and the Arabic banner on a phone

TecnoStack Cookie Consent for Magento 2

A consent banner and privacy preference centre that drops into any Magento 2 theme.
Google Consent Mode v2 · script, iframe and pixel blocking · server-side consent log · Arabic and English

Packagist Magento 2.4.3 – 2.4.8 PHP 7.4 – 8.4 Google Consent Mode v2 Arabic RTL Tests

Features · Storefront · Admin · Quick start · Documentation · Changelog

✨ Features

🎨 Any theme
The UI lives in an isolated Shadow DOM: theme CSS cannot break it, and it cannot break the theme. Restyle it from the admin or with ::part().
⚡ Cache friendly
Full page cache and Varnish safe. Every visitor gets the same HTML; every decision is taken in the browser.
🛡️ Nothing runs before consent
Scripts, iframes and pixels wait for their category: declarative markup, URL patterns and Magento's own Google tags.
📈 Google Consent Mode v2
default before any tag, update after every choice, plus a GTM dataLayer stream and an optional OneTrust shim.
🌍 Arabic and English
Right to left mirrored from the store locale, even when the theme never sets dir="rtl". Every text is translatable per store view.
🧾 Consent log
A server-side record of every choice, with hashed IPs, retention clean-up, filters and CSV export.
🔒 CSP safe
One tiny inline script through SecureHtmlRenderer; colours and custom CSS go through CSSOM, never inline styles.
🧩 Widgets
A Cookie Table for your cookie policy page and a Cookie Settings Link for any footer, CMS page or block.
🪶 Dependency free
Plain JavaScript: no jQuery, RequireJS, Knockout or Alpine. PHP 7.4 to 8.4 from the same code.

🖥️ Storefront

Storefront slideshow: banner, preference centre, category details, blocked video, Arabic right-to-left views and mobile screens on the Luma theme

Stock Luma theme · English and Arabic · desktop and mobile

🛠️ Admin

Admin slideshow: menu, consent categories, category editor, Arabic store view, cookie registry, cookie editor, CSV import, consent log, every settings group and the Cookie Table widget

Browse every admin screen
Content menu
Content › Cookie Consent
Consent categories grid
Consent categories
Category editor
Category editor
Arabic store view texts
Arabic store view
Cookie registry grid
Cookie registry
Cookie editor
Cookie editor
CSV import
CSV import
Consent log grid
Consent log
Insert the Cookie Table widget
Cookie Table widget
General settings
Settings · general
Banner settings
Settings · banner
Preference centre settings
Settings · preference centre
Design settings
Settings · design
Integration settings
Settings · integrations
Consent log settings
Settings · consent log

🚀 Quick start

composer require tecnostack/module-cookie-consent
bin/magento module:enable TecnoStack_CookieConsent
bin/magento setup:upgrade
bin/magento cache:flush

The banner shows on the next page load with four default categories and a starter cookie registry. Then work through the post-install checklist: store name, cookie policy page, your real cookies and your tags.

📚 Documentation

Getting started Integrating Operating
1. Compatibility 5. Theme integration 9. Consent log
2. Installation 6. Blocking scripts, iframes and pixels 12. Performance and caching
3. How it works 7. Google Consent Mode v2 and GTM 13. Security and privacy
4. Configuration reference 8. JavaScript API 14. Testing
10. Consent models 11. Translations 15. Troubleshooting · 16. Uninstall · 17. Legal note

1. Compatibility

Supported
Magento Open Source / Adobe Commerce 2.4.3 – 2.4.8
PHP 7.4, 8.1, 8.2, 8.3, 8.4
Themes Luma, Blank and custom themes (tested on Luma and a commercial theme); Hyvä and Breeze by design: Shadow DOM UI, dependency-free script, Hyvä layout handle included
Full page cache Built-in and Varnish
Browsers Current Chrome, Edge, Safari, Firefox; Shadow DOM is attached by script where declarative Shadow DOM is not supported
Headless / PWA Studio Not rendered automatically; the JavaScript API and the log endpoint can be reused

Magento_Csp, Magento_GoogleGtag, Magento_GoogleAnalytics and Hyvä are optional (soft integrations).

2. Installation

# Option A: composer (artifact or private repository)
composer require tecnostack/module-cookie-consent

# Option B: copy the module
mkdir -p app/code/TecnoStack/CookieConsent
cp -R <source>/* app/code/TecnoStack/CookieConsent/

bin/magento module:enable TecnoStack_CookieConsent
bin/magento setup:upgrade
bin/magento setup:di:compile                         # production mode
bin/magento setup:static-content:deploy ar_SA en_US -f   # your locales
bin/magento cache:flush

Installation creates five tables, the four default categories (IDs 1-4, English labels plus Arabic labels for every store view with an Arabic locale) and a starter cookie registry.

Post-install checklist

  1. Stores > Configuration > TecnoStack > Cookie Consent: set the Store display name per store view (it replaces {{store_name}}) and pick the Cookie policy page.
  2. Create or translate the cookie policy CMS page and insert the Cookie Table widget.
  3. Review the category texts per store view in Content > Cookie Consent > Categories, especially the second Targeting paragraph (social partners / joint responsibility).
  4. Fill the Cookie Registry with the cookies your store really sets (DevTools > Application, or a scanner). Names ending with * are wildcards (_ga_*).
  5. Hold back third-party snippets: mark them with type="text/plain" data-ts-category="..." or add URL patterns to auto-blocking (section 6).
  6. GTM: create the tsActiveGroups variable and the consent triggers (section 7), or enable the OneTrust shim when migrating from OneTrust.
  7. Verify in GTM Preview / Tag Assistant that the Consent Mode default and update commands fire.
  8. Make sure a "Cookie settings" link is visible in your footer (section 5).
  9. After any change that needs fresh consent, increase Consent version.

3. How it works

Moment What happens
First visit (implied / notice model) All active categories are granted by default, ts_consent is written with ic=0, the banner is shown.
First visit (explicit model) Only required categories are granted; nothing optional runs; no consent cookie is written.
Accept / Allow all / Confirm my choices ts_consent and ts_consent_closed are written, blocked content of granted categories is released, Consent Mode is updated, dataLayer and DOM events are fired, the choice is logged.
A category is revoked Its registry cookies are deleted, Consent Mode is updated and, when code of that category already ran, the page reloads once without it.
Consent version changed The banner shows again and the previous choice no longer applies.

Cookies set by the module

Cookie Content
ts_consent v=1&id=<uuid>&groups=1%3A1%2C2%3A1%2C3%3A0%2C4%3A1&ts=<ISO>&ic=1&model=implied&store=ar
ts_consent_closed ISO timestamp, written once the visitor acted on the banner
user_allowed_save_cookie Magento's own flag, so wishlist, compare and recently viewed keep working. Written on the first visit in the implied and notice models, and only after the visitor's choice in the explicit model

Attributes: path=/, SameSite=Lax, Secure on HTTPS, Magento's cookie domain when it matches the host, lifetime = "Consent lifetime (days)".

4. Configuration reference

Stores > Configuration > TecnoStack > Cookie Consent (tecnostack_cookieconsent/*). Every text is store-view scoped; empty texts use the translated defaults (English and Arabic are included).

Group Main fields
General Enable, Consent model (implied / explicit / notice), Consent version, Consent lifetime, cookie names, Store display name, Cookie policy page, URL override, Disable Magento native cookie notice, Excluded paths
Banner Position (bottom-start / bottom-end / bottom-center / bottom-full-width), title, text, three bullets, button labels
Preference Centre Drawer side (left / right / inline-start / inline-end), Close on overlay click, Show "Allow all", Show cookie list per category, texts, footer link options
Design Primary button colours, accent, toggle OFF, border, expanded row background, overlay opacity, custom CSS
Integrations Consent Mode v2, wait_for_update, regions, dataLayer events and variable name, url_passthrough, OneTrust shim, Google tag handling, auto-block patterns
Consent Log Enable, log implied defaults, hashed IP, user agent, retention (days)

Admin pages: Content > Cookie Consent > Categories / Cookie Registry / Consent Log / Settings. ACL resources: TecnoStack_CookieConsent::categories, ::cookies, ::logs, ::logs_export, ::config.

5. Theme integration (any theme)

The banner and preference centre

Nothing to do: the UI is injected in after.body.start and isolated in a Shadow DOM. It inherits only the theme's font family and the text direction of the store locale (RTL for Arabic, Persian, Hebrew, Urdu...), even when the theme does not set dir on <html>.

Restyle it without touching the theme:

  • Admin > Design: colours and overlay opacity, applied as CSS custom properties.
  • Admin > Design > Custom CSS: rules applied inside the Shadow DOM, e.g. .tscc-banner { border-radius: 8px; }.
  • From the theme, through ::part():
#ts-cookie-consent::part(banner) { border-radius: 12px; }
#ts-cookie-consent::part(button) { text-transform: uppercase; }
#ts-cookie-consent::part(drawer) { width: 420px; }

Parts: root, banner, banner-body, banner-title, banner-actions, button, accept-button, settings-button, ok-button, overlay, drawer, drawer-title, close-button, more-info-link, allow-all-button, accordion, always-active, switch, drawer-footer, confirm-button, placeholder, placeholder-button.

The "Cookie settings" footer link

The preference centre must stay reachable after the first choice. Pick what fits the theme:

Theme How
Luma, Blank and themes based on them Automatic: added to the footer_links block ("Footer link: add automatically" = Yes).
Hyvä Automatic: hyva_default.xml moves the link into the Hyvä footer container.
Any other theme Footer link: CSS selector — the link is appended to every element matching the selector, e.g. .page-footer .footer-links ul or .page-footer ul:has(a[href*="privacy"]) (inside a list it becomes a list item).
Anywhere (CMS) The Cookie Settings Link widget: {{widget type="TecnoStack\CookieConsent\Block\Widget\SettingsLink" label="Cookie settings"}}
Anywhere (code) Any element with the data-ts-cookie-settings attribute, or TsConsent.openPreferences().

The Cookie Table widget

Insert it in any CMS page or block: Insert Widget > Cookie Table (with Page Builder, inside an HTML Code element). The table keeps the theme's own table styles (data table, stacked rows with data-th labels on mobile). The module only pins the cell alignment, through a small stylesheet linked next to the table (css/cookie-table.css), because Magento's table library sets th { text-align: left } even in RTL stores. Latin values such as 2 years or _ga_* are wrapped in <bdi>, so they keep their order in Arabic store views; this applies to the cookie list in the preference centre too.

6. Blocking scripts, iframes and pixels

Declarative markup (recommended)

<script type="text/plain" data-ts-category="performance" src="https://www.clarity.ms/tag/XXXX"></script>

<script type="text/plain" data-ts-category="targeting">
  !function(f,b,e,v,n,t,s){/* Meta Pixel */}(window, document,'script','https://connect.facebook.net/en_US/fbevents.js');
</script>

<iframe data-ts-category="functional" data-src="https://www.youtube-nocookie.com/embed/ID" title="Video"></iframe>

<img data-ts-category="targeting" data-src="https://www.facebook.com/tr?id=XXXX&ev=PageView&noscript=1" alt="" width="1" height="1">
  • Scripts are re-created in document order; inline code runs after the external file it depends on.
  • data-ts-category="performance,targeting" releases only when all listed categories are granted.
  • data-ts-type="module" restores a non-classic type.
  • Blocked iframes show a placeholder ("This content is blocked by your cookie settings.") with a Cookie settings button; granting the category loads the iframe without a reload.

Auto-blocking by URL pattern

For code injected by modules you cannot edit, add one pattern|categoryCode per line:

connect.facebook.net|targeting
analytics.tiktok.com|targeting
sc-static.net|targeting
clarity.ms|performance
hotjar.com|performance
  • Server side: matching <script src> tags, inline scripts and text/x-magento-init components are rewritten to the blocked format in every storefront HTML response.
  • Client side: a guard in the head script holds back matching scripts inserted later by other scripts.
  • While no pattern is configured, neither runs.

Remember to allow the hosts of tags you release in your project's csp_whitelist.xml when Magento_Csp is enabled.

Magento's own Google tags

By default ("Let Consent Mode govern Google tags" = Yes) Magento's Google Analytics / Google Ads tags load normally and obey the Consent Mode signals. Set it to No and "Block Magento Google tags by category" = Yes to hold them back instead (GA with performance, Google Ads with targeting). Magento 2.4.5+ renders the Google Ads snippets with plain template blocks; they are matched by layout name (gtag_google_adwords, gtag.google.adwords). Add other blocks through di.xml:

<type name="TecnoStack\CookieConsent\Observer\BlockNamedTagsObserver">
    <arguments>
        <argument name="blockCategories" xsi:type="array">
            <item name="my.pixel.block" xsi:type="string">targeting</item>
        </argument>
    </arguments>
</type>

Strict CSP (Magento 2.4.7+ checkout): a released script keeps only the nonce it was rendered with, so injected markup can never borrow the page nonce. Render your own blocked inline scripts through $secureRenderer->renderTag('script', ['type' => 'text/plain', 'data-ts-category' => '...'], $js, false) so they carry the nonce on those pages.

7. Google Consent Mode v2 and GTM

The head script is printed right after <meta charset>, before every other script in <head> (including a GTM snippet pasted into Content > Design > Configuration > HTML Head > Scripts and Style Sheets), and sends:

gtag('consent', 'default', {
  ad_storage, ad_user_data, ad_personalization,        // targeting
  analytics_storage,                                    // performance
  functionality_storage, personalization_storage,       // functional
  security_storage: 'granted',
  wait_for_update: 500
});

Implied model: granted until the visitor opts out. Explicit model: denied until the visitor consents. After every choice: gtag('consent', 'update', {...}). The mapping is editable per category (Content > Cookie Consent > Categories > Google Consent Mode types). A type mapped to several categories is granted only when all of them are granted.

dataLayer events

Event When Payload
ts_consent_ready Every page ts_consent: {performance, functional, targeting, version, interacted}, ts_active_groups
ts_consent_update After Accept, Allow all, Confirm or TsConsent.reset() same + ts_consent_action
OneTrustGroupsUpdated Same moments, OneTrust shim only OnetrustActiveGroups

GTM setup

  1. Variable TS Active Groups: type JavaScript Variable, name tsActiveGroups (e.g. ,1,2,4,).
  2. Trigger TS - Performance granted: Custom Event, event name ts_consent_ready|ts_consent_update (use regex matching), fires when TS Active Groups contains ,2,. Create the same for ,3, (functional) and ,4, (targeting).
  3. Attach non-Google tags (Meta, TikTok, Snap, Clarity...) to those triggers. Google tags follow Consent Mode on their own.
  4. Migrating from OneTrust: enable OneTrust compatibility shim; existing triggers on OnetrustActiveGroups and OneTrustGroupsUpdated keep working.

8. JavaScript API

TsConsent.getConsent();            // {version, id, groups: {necessary: true, performance: false, ...}, ts, interacted, model}
TsConsent.has('targeting');        // boolean
TsConsent.onConsent('performance', fn); // runs fn now if granted, or once when granted later
TsConsent.onChange(fn);            // after every save, with the consent object
TsConsent.openPreferences();       // opens the preference centre
TsConsent.acceptAll();
TsConsent.save({performance: true, functional: false, targeting: true});
TsConsent.reset();                 // deletes both consent cookies and shows the banner again

document.addEventListener('ts:consent:update', (event) => console.log(event.detail));

window.tsActiveGroups is available synchronously from the head script (before GTM loads).

9. Consent log

  • Every Accept / Allow all / Confirm / withdraw is posted to POST /cookieconsent/log/save (fetch with keepalive, navigator.sendBeacon fallback). The endpoint validates Magento's form key, whitelists every field, rejects bodies over 4 KB, allows 20 writes per consent ID and 300 per client IP per hour, and is never cached.
  • Stored: consent ID, store view, customer ID (when logged in), action, category states, version, model, page path (no query string), optional salted SHA-256 of the IP (crypt/key is the salt), optional user agent.
  • Content > Cookie Consent > Consent Log: filters, full-text search on the consent ID, mass delete, CSV / Excel XML export (ACL TecnoStack_CookieConsent::logs_export).
  • Retention: a daily cron job (15 3 * * *) deletes records older than "Retention (days)" (default 1095).
  • Erasure request:
bin/magento tecnostack:cookie-consent:purge --customer-id=123
bin/magento cron:run --group=default        # runs the retention job

Behind a load balancer or CDN, configure Magento's Magento\Framework\HTTP\PhpEnvironment\RemoteAddress alternativeHeaders (di.xml) so the real client IP is hashed.

Extending: decorate or replace TecnoStack\CookieConsent\Api\ConsentLoggerInterface (di.xml plugin or preference) to forward records to a CRM or a cross-device consent store.

10. Consent models and per-country rules

Model Before the visitor chooses
implied (default) All categories active; toggles ON
explicit Only required categories active; optional toggles OFF
notice All active; the banner shows a single "OK" button

TecnoStack\CookieConsent\Api\ConsentModelResolverInterface::resolve(int $storeId) decides the model; the default implementation returns the store view setting. A resolver that varies per request (for example by CF-IPCountry) must also vary the full page cache on the same input (HTTP context / Varnish VCL), because the decision is embedded in the cached page.

11. Translations

i18n/en_US.csv and i18n/ar_SA.csv hold every storefront default. Magento's translation order applies: module < language pack < theme < inline translation, so a language pack that already translates generic words ("Back", "More information", "OK") wins. Set the exact wording in the admin text fields when needed.

12. Performance and caching

  • No layout handle is uncacheable; the consent cookie is never part of the cache key.
  • Banner, preference centre and JSON config are cached in block_html and the full page cache with the tag tscc_config, which is purged (built-in FPC and Varnish) when the configuration, a category or a registry entry changes.
  • The banner is position: fixed: no layout shift.
  • Assets: one deferred script and one stylesheet loaded inside the Shadow DOM.

13. Security and privacy

  • All output is escaped; category descriptions allow only p, br, strong, a, ul, li.
  • Custom CSS cannot close its style element (< is removed) and is applied through CSSOM.
  • No personal data in cookies; the consent ID is a random UUID v4.
  • Admin controllers are ACL protected; the log endpoint is CSRF protected and rate limited.

14. Testing

# Unit (from the Magento root)
vendor/bin/phpunit -c dev/tests/unit/phpunit.xml.dist app/code/TecnoStack/CookieConsent/Test/Unit

# Integration (requires dev/tests/integration/etc/install-config-mysql.php)
cd dev/tests/integration && ../../../vendor/bin/phpunit ../../../app/code/TecnoStack/CookieConsent/Test/Integration

# End to end (Playwright): store views /ar/ and /en/, desktop and mobile
cd app/code/TecnoStack/CookieConsent/Test/E2E
npm install && npx playwright install chromium
TSCC_BASE_URL=https://your-store.test npx playwright test

The E2E suite (E1-E17) adds its own fixtures to the page (blocked scripts and a blocked iframe) and simulates a consent version bump or the explicit model by rewriting the frontend config in the response, so it needs no admin or database changes.

15. Troubleshooting

Symptom Check
No banner Module enabled for the store view? Excluded path? Already consented (delete ts_consent_closed)?
Banner unstyled for a moment The stylesheet is fetched by the Shadow DOM; make sure static content is deployed for the theme/locale.
No "Cookie settings" link Theme without footer_links: set "Footer link: CSS selector", or use the widget (section 5).
Blocked script never runs data-ts-category must contain existing category codes; a typo keeps it blocked (safe default).
Consent log 403 The request must carry Magento's form_key cookie; check that no proxy strips cookies on POST.
GA/Ads not blocked "Let Consent Mode govern Google tags" is Yes by default; Consent Mode is the mechanism then.

16. Uninstall

bin/magento module:disable TecnoStack_CookieConsent
bin/magento setup:upgrade

Remove the code (or composer remove tecnostack/module-cookie-consent). The tables tecnostack_cookie_* are kept with your consent records; drop them manually if you no longer need them.

17. Legal note

The default implied (opt-out) model mirrors the reference storefront in Saudi Arabia, where optional categories are active until the visitor turns them off. Saudi Arabia's Personal Data Protection Law (PDPL), the UAE PDPL, the GDPR / ePrivacy rules for EU visitors and other laws may require explicit opt-in for some uses. Use the explicit model, or a ConsentModelResolverInterface per-country rule, where required, and have your legal adviser approve the final texts. This module is a technical tool, not legal advice.

Built by TecnoStack · Proprietary software, all rights reserved · Report an issue