Dependency-free consent banner that integrates with Google Consent Mode V2 and can optionally load GTM after consent defaults are in place.
What it does:
- Initializes conservative Consent Mode defaults before tags run.
- Persists user choices in
localStoragewith a versioned envelope. - Applies stored consent on subsequent visits.
- Ignores malformed, expired, or legacy consent storage safely.
- Supports Do Not Track (
navigator.doNotTrack) and Global Privacy Control (navigator.globalPrivacyControl). - Exposes a small public API (
window.cookieconsent). - Supports optional region-scoped default consent behavior.
- Loads GTM automatically after consent boot when
gtmIdis configured. - Skips GTM loading when no GTM ID is configured.
What it is not:
- Not a full CMP.
- Does not provide IAB TCF strings, vendor lists, consent records, preference-center versioning, scan-based cookie categorization, or proof-of-consent workflows.
- Include
cookieconsent.css. - Define
window.cookieconsentConfig = { gtmId: 'GTM-XXXXXXX' }beforecookieconsent.js. - Include
cookieconsent.jsin<head>(not deferred). - Add a
.cookie-consent-banner-openelement anywhere in the page. - Enable Consent Mode in GTM and align tag consent requirements.
Minimal example:
<link rel="stylesheet" href="cookieconsent.css" />
<script>
window.cookieconsentConfig = {
gtmId: 'GTM-XXXXXXX'
};
</script>
<script src="cookieconsent.js"></script>Recommended order in <head>:
cookieconsent.csswindow.cookieconsentConfig = { gtmId: ... }cookieconsent.js- Other analytics/marketing scripts only if they are also consent-aware
Why: this ensures consent defaults are available before GTM or other tags execute.
In cookieconsent.js:
USE_REGION_LIST = false: default-deny is applied globally.USE_REGION_LIST = true: default-deny is applied only toCONSENT_REGION_LIST.
Update CONSENT_REGION_LIST to match your legal/compliance policy.
Set either:
window.cookieconsentConfig = { gtmId: 'GTM-XXXXXXX' }window.COOKIECONSENT_GTM_ID = 'GTM-XXXXXXX'
cookieconsent.js loads GTM once consent defaults and any valid stored consent have been applied.
If neither value is set, GTM is not loaded by this script.
Important:
- Consent Mode still depends on GTM being configured correctly.
- Tags must be mapped to the right consent requirements inside GTM.
Copy/paste examples:
<script>
window.cookieconsentConfig = { gtmId: 'GTM-XXXXXXX' };
</script><script>
window.COOKIECONSENT_GTM_ID = 'GTM-XXXXXXX';
</script>window.cookieconsent.show()
- Opens the banner.
window.cookieconsent.hide()
- Hides the banner.
window.cookieconsent.setConsent(selection)
- Programmatically sets consent and persists it.
selection shape:
{
necessary: true,
analytics: true | false,
preferences: true | false,
marketing: true | false,
partners: true | false
}Selections are mapped to Google Consent Mode keys:
marketing->ad_storage,ad_user_dataanalytics->analytics_storagepartners->ad_personalizationpreferences->personalization_storagenecessary->functionality_storage,security_storage
DNT/GPC can force denial for related fields.
Consent is saved in localStorage['consentMode'] as a versioned envelope:
{
schemaVersion: 1,
createdAt: "2026-06-05T00:00:00.000Z",
updatedAt: "2026-06-05T00:00:00.000Z",
source: "user_action",
expiresAt: "2027-06-05T00:00:00.000Z",
consentMode: {
ad_storage: "denied",
analytics_storage: "denied",
ad_user_data: "denied",
ad_personalization: "denied",
functionality_storage: "granted",
personalization_storage: "denied",
security_storage: "granted"
},
selection: {
necessary: true,
analytics: false,
preferences: false,
marketing: false,
partners: false
}
}Legacy raw consent objects are migrated automatically when read. Malformed or expired values are ignored and cleared.
You can optionally override storage retention with:
<script>
window.cookieconsentConfig = {
gtmId: 'GTM-XXXXXXX',
consentStorageMaxAgeDays: 365
};
</script>- Clear
localStorage['consentMode']and reload. - Confirm the banner appears.
- Test accept all, reject all, and custom selection flows.
- Verify
localStorage['consentMode']updates correctly. - Verify GTM loads after consent boot.
- Verify reopening the banner with
.cookie-consent-banner-open. - Confirm malformed storage is cleared instead of breaking startup.
Serve the repo locally or open smoke-test.html from the repo root in a browser and click Run smoke tests.
The page exercises:
- Fresh boot banner rendering
- Legacy storage migration
- Malformed storage cleanup
- Public API persistence via
window.cookieconsent.setConsent(selection)
Run the repo checks with:
npm testThat command runs the static repository verifier, the runtime consent harness, and node --check on cookieconsent.js.
It also runs the Playwright browser suite against the real banner in a headless Chromium session.
If Chromium and Firefox are not installed locally yet, run npx playwright install chromium firefox once before npm test.
CI is defined in .github/workflows/ci.yml.
Tagged releases also publish a source tarball from .github/workflows/release.yml.
Quick checks in browser devtools:
// Confirm stored consent state:
localStorage.getItem('consentMode')
// Confirm GTM script injected by cookieconsent.js:
document.querySelector('script[data-gtm-loader]')- Loading
cookieconsent.jsafter GTM or other trackers. - Defining GTM ID config after loading
cookieconsent.js. - Forgetting to set either
window.cookieconsentConfig.gtmIdorwindow.COOKIECONSENT_GTM_ID. - Assuming the region list is legally complete without compliance review.
- Assuming this project is a CMP.
- Banner does not appear:
- Check whether
localStorage['consentMode']already exists. - Call
window.cookieconsent.show()manually to verify rendering.
- Check whether
- GTM does not load:
- Check console warning for missing GTM ID.
- Confirm
window.cookieconsentConfig.gtmId(orwindow.COOKIECONSENT_GTM_ID) is defined beforecookieconsent.js. - Confirm no CSP rule blocks
https://www.googletagmanager.com.
- Add a UI state for categories that are effectively unavailable because
Do Not TrackorGlobal Privacy Controlis enabled, likely by disabling those controls and labeling why the choice cannot be applied as selected.
This implementation provides technical controls for consent signaling and regional scoping. It is not legal advice. Validate region strategy, defaults, storage retention, and tagging behavior with legal/compliance stakeholders.
MIT
Tip: index.html in this repo demonstrates the config-based flow.
