This guide shows you how to update Consent Mode v2 state from custom JavaScript, including via GTM Kit’s own JS API, and how to listen for consent changes elsewhere on the page.
When you need this
You are integrating a CMP that GTM Kit does not natively support yet, or you have custom logic for granting consent (e.g. a B2B portal where consent is implied by login). In both cases, you need to tell Google’s tags “the user just changed consent” so they re-evaluate whether to fire.
Option 1: Call gtag directly
The standard, vendor-neutral approach. Anywhere in your JavaScript:
gtag('consent', 'update', {
'ad_storage': 'granted',
'ad_user_data': 'granted',
'ad_personalization': 'granted',
'analytics_storage': 'granted'
});
gtag is defined globally as soon as the GTM Consent Mode block emits. You can call this from a button click handler, a CMP’s “consent saved” callback, or any other source.
This works regardless of GTM Kit. It is what Google’s own documentation tells you to do.
Option 2: Use GTM Kit’s JS API
GTM Kit 2.9 exposes a small wrapper that fires a CustomEvent in addition to calling gtag:
window.gtmkit.consent.update({
ad_storage: 'granted',
analytics_storage: 'granted'
});
This:
- Calls
gtag('consent', 'update', {...})with your payload. - Fires a
gtmkit:consent:updatedCustomEvent onwindowso other scripts can listen for it.
The advantage over calling gtag directly is the CustomEvent. If you have other scripts on the page that need to react to consent changes (e.g. a chat widget that should only load after functionality_storage is granted), they can listen instead of polling.
Option 3: Listen for consent updates
Anywhere on the page:
window.addEventListener('gtmkit:consent:updated', function (e) {
console.log('Consent changed:', e.detail);
// e.detail contains the payload that was passed to update()
});
event.detail is the partial update passed to update(); it can be a single category. To read the full current state across all updates, use window.gtmkit.consent.state.
Use this to gate the loading of third-party scripts that should only run with consent, e.g.:
window.addEventListener('gtmkit:consent:updated', function (e) {
if (e.detail && e.detail.functionality_storage === 'granted') {
loadChatWidget();
}
});
Option 4: Read the current state
window.gtmkit.consent.state is a snapshot of the seven Consent Mode v2 categories, kept in sync with calls to update(). Read it at the point you need it:
if ( window.gtmkit?.consent?.state.analytics_storage === 'granted' ) {
// tracker can fire
}
state is a snapshot, not a getter; read it at the point you need it, or subscribe to gtmkit:consent:updated if you need to react to changes.
Wiring it up to a CMP that GTM Kit does not support natively
A typical pattern: your CMP fires a callback when the user saves consent. Wrap that callback to call GTM Kit’s update API:
myCmp.onConsentSaved(function (cmpState) {
window.gtmkit.consent.update({
ad_storage: cmpState.marketing ? 'granted' : 'denied',
ad_user_data: cmpState.marketing ? 'granted' : 'denied',
ad_personalization: cmpState.marketing ? 'granted' : 'denied',
analytics_storage: cmpState.statistics ? 'granted' : 'denied',
functionality_storage: cmpState.preferences ? 'granted' : 'denied',
personalization_storage: cmpState.preferences ? 'granted' : 'denied',
security_storage: 'granted'
});
});
Map the CMP’s category names (left-hand side of the conditional) to Consent Mode v2’s category names (left-hand side of the object).
Verify it worked
Open GTM Tag Assistant. The Consent column on each tag’s row updates live as you call update.
For your own integration, add a temporary log:
window.addEventListener('gtmkit:consent:updated', e => console.log('update', e.detail));
Trigger your CMP banner, click Accept, and confirm the log fires with the categories you expected.
Common issues
window.gtmkit is undefined. Either GTM Kit’s master toggle is off (no consent block emitted, no JS surface either), or your script runs before GTM Kit’s. Wrap your update logic in DOMContentLoaded:
document.addEventListener('DOMContentLoaded', function () {
if (window.gtmkit && window.gtmkit.consent) {
// ...
}
});
gtag is undefined. Same root cause. Either Consent Mode is off, or the consent block has not run yet. Wait for DOMContentLoaded or check existence first.
Tags still do not fire after update. Two possibilities. (1) The category you granted is not actually the one the tag requires. Check the tag’s Consent Settings in GTM. (2) The tag’s wait_for_update window has already elapsed and the tag fired with denied state. Increase wait_for_update (see Set per-category consent defaults) or call update earlier.
The CustomEvent fires twice. Your CMP’s “consent saved” callback fired twice. Some CMPs fire on every preference change and on save. Debounce in your callback.