---
title: "Push consent updates from custom code"
date: 2026-05-04
author: "GTM Kit"
---

# Push consent updates from custom code

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:

1. Calls `gtag('consent', 'update', {...})` with your payload.
2. Fires a `gtmkit:consent:updated` CustomEvent on `window` so 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](https://gtmkit.com/documentation/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.

## Related articles

- [Turn on Google Consent Mode v2 defaults](https://gtmkit.com/documentation/turn-on-google-consent-mode-v2-defaults/)
- [Set per-category consent defaults](https://gtmkit.com/documentation/set-per-category-consent-defaults/)
- [Coexist with a CMP](https://gtmkit.com/documentation/coexist-with-a-cmp/)