---
title: "Cookie Consent"
description: "Gate scripts behind user consent and drive vendor-native consent APIs through a typed per-script `consent` object."
canonical_url: "https://scripts.nuxt.com/docs/guides/consent"
last_updated: "2026-07-07T14:43:03.537Z"
---

<callout icon="i-heroicons-play" target="_blank" to="https://stackblitz.com/github/nuxt/scripts/tree/main/examples/cookie-consent">

Try the live [Cookie Consent Example](https://stackblitz.com/github/nuxt/scripts/tree/main/examples/cookie-consent) or [Granular Consent Example](https://stackblitz.com/github/nuxt/scripts/tree/main/examples/granular-consent) on [StackBlitz](https://stackblitz.com).

</callout>

## Two complementary primitives

Nuxt Scripts ships two consent primitives that work together:

1. **useScriptTriggerConsent()**: a binary load gate. The script only starts loading after consent is granted.
2. **Per-script consent object** returned from every consent-aware `useScriptX()`. A vendor-native, typed API for granting, revoking, or updating consent categories at runtime. Paired with each script's `defaultConsent` option for the initial policy applied *before* the vendor's init call.

Each vendor exposes its own consent dialect (Google Consent Mode v2 for GA/GTM/Bing, binary grant/revoke for Meta, three-state for TikTok, `setConsentGiven`/`forgetConsentGiven` for Matomo, `opt_in`/`opt_out` for Mixpanel/PostHog, cookie toggle for Clarity). You wire each explicitly.

## Binary load gate

The simplest usage matches the classic cookie-banner flow: load the script only after the user clicks accept.

<code-group>

```ts [utils/cookie.ts]
export const scriptsConsent = useScriptTriggerConsent()
```

```vue [app.vue]
<script setup lang="ts">
import { scriptsConsent } from '#imports'

useScript('https://www.google-analytics.com/analytics.js', {
  trigger: scriptsConsent,
})
</script>
```

```vue [components/cookie-banner.vue]
<script setup lang="ts">
import { scriptsConsent } from '#imports'
</script>

<template>
  <button @click="scriptsConsent.accept()">
    Accept Cookies
  </button>
</template>
```

</code-group>

### Reactive source

Pass a `Ref<boolean>` if an external store owns the state.

```ts
const agreedToCookies = ref(false)
const consent = useScriptTriggerConsent({ consent: agreedToCookies })
```

### Revoking

Consent revocation flips the reactive `consented` ref. Once the load-gate promise has resolved the script has loaded; watch `consented` if you need to tear down on revoke.

```vue
<template>
  <div v-if="scriptsConsent.consented.value">
    <button @click="scriptsConsent.revoke()">
      Revoke Consent
    </button>
  </div>
  <button v-else @click="scriptsConsent.accept()">
    Accept Cookies
  </button>
</template>
```

### Delaying the load after consent

```ts
const consent = useScriptTriggerConsent({
  consent: agreedToCookies,
  postConsentTrigger: () => new Promise<void>(resolve =>
    setTimeout(resolve, 3000),
  ),
})
```

## Per-script consent API

Every consent-aware `useScriptX()` returns a `consent` object typed to the vendor's native API. Combine it with `defaultConsent` for the initial policy (applied in `clientInit` before the vendor fires its first call) and call `consent.*` from your cookie banner to update. For GCMv2 scripts (Google Analytics, Google Tag Manager), `consent.default(state)` is also available for runtime-derived defaults; both methods validate input against the canonical GCMv2 schema and warn via `consola` on unknown keys or non-`granted`/`denied` values.

```ts
const { consent } = useScriptGoogleAnalytics({
  id: 'G-XXXXXXXX',
  defaultConsent: { ad_storage: 'denied', analytics_storage: 'denied' },
})

function onAcceptAll() {
  consent.update({
    ad_storage: 'granted',
    ad_user_data: 'granted',
    ad_personalization: 'granted',
    analytics_storage: 'granted',
  })
}
```

### Per-vendor surface

<table>
<thead>
  <tr>
    <th>
      Script
    </th>
    
    <th>
      <code>
        defaultConsent
      </code>
    </th>
    
    <th>
      Runtime <code>
        consent.*
      </code>
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Google Analytics
    </td>
    
    <td>
      <code className="language-html shiki shiki-themes github-light github-light material-theme-palenight" language="html" style="">
        <span class="sqjlB">
          Partial
        </span>
        
        <span class="sx-uw">
          <
        </span>
        
        <span class="sFfpx">
          ConsentState
        </span>
        
        <span class="sx-uw">
          >
        </span>
      </code>
      
       (GCMv2)
    </td>
    
    <td>
      <code className="language-ts shiki shiki-themes github-light github-light material-theme-palenight" language="ts" style="">
        <span class="sqjlB">
          consent
        </span>
        
        <span class="sx-uw">
          .
        </span>
        
        <span class="s0YkB">
          default
        </span>
        
        <span class="sqjlB">
          (state)
        </span>
      </code>
      
       / <code className="language-ts shiki shiki-themes github-light github-light material-theme-palenight" language="ts" style="">
        <span class="sqjlB">
          consent
        </span>
        
        <span class="sx-uw">
          .
        </span>
        
        <span class="s0YkB">
          update
        </span>
        
        <span class="sqjlB">
          (state)
        </span>
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Google Tag Manager
    </td>
    
    <td>
      <code className="language-html shiki shiki-themes github-light github-light material-theme-palenight" language="html" style="">
        <span class="sqjlB">
          Partial
        </span>
        
        <span class="sx-uw">
          <
        </span>
        
        <span class="sFfpx">
          ConsentState
        </span>
        
        <span class="sx-uw">
          >
        </span>
      </code>
      
       (GCMv2)
    </td>
    
    <td>
      <code className="language-ts shiki shiki-themes github-light github-light material-theme-palenight" language="ts" style="">
        <span class="sqjlB">
          consent
        </span>
        
        <span class="sx-uw">
          .
        </span>
        
        <span class="s0YkB">
          default
        </span>
        
        <span class="sqjlB">
          (state)
        </span>
      </code>
      
       / <code className="language-ts shiki shiki-themes github-light github-light material-theme-palenight" language="ts" style="">
        <span class="sqjlB">
          consent
        </span>
        
        <span class="sx-uw">
          .
        </span>
        
        <span class="s0YkB">
          update
        </span>
        
        <span class="sqjlB">
          (state)
        </span>
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Bing UET
    </td>
    
    <td>
      <code>
        { ad_storage }
      </code>
    </td>
    
    <td>
      <code className="language-ts shiki shiki-themes github-light github-light material-theme-palenight" language="ts" style="">
        <span class="sqjlB">
          consent
        </span>
        
        <span class="sx-uw">
          .
        </span>
        
        <span class="s0YkB">
          update
        </span>
        
        <span class="sqjlB">
          (
        </span>
        
        <span class="sx-uw">
          {
        </span>
        
        <span class="sqjlB">
          ad_storage
        </span>
        
        <span class="sx-uw">
          }
        </span>
        
        <span class="sqjlB">
          )
        </span>
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Meta Pixel
    </td>
    
    <td>
      <code>
        'granted' | 'denied'
      </code>
    </td>
    
    <td>
      <code className="language-ts shiki shiki-themes github-light github-light material-theme-palenight" language="ts" style="">
        <span class="sqjlB">
          consent
        </span>
        
        <span class="sx-uw">
          .
        </span>
        
        <span class="s0YkB">
          grant
        </span>
        
        <span class="sqjlB">
          ()
        </span>
      </code>
      
       / <code className="language-ts shiki shiki-themes github-light github-light material-theme-palenight" language="ts" style="">
        <span class="sqjlB">
          consent
        </span>
        
        <span class="sx-uw">
          .
        </span>
        
        <span class="s0YkB">
          revoke
        </span>
        
        <span class="sqjlB">
          ()
        </span>
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      TikTok Pixel
    </td>
    
    <td>
      <code>
        'granted' | 'denied' | 'hold'
      </code>
    </td>
    
    <td>
      <code className="language-ts shiki shiki-themes github-light github-light material-theme-palenight" language="ts" style="">
        <span class="sqjlB">
          consent
        </span>
        
        <span class="sx-uw">
          .
        </span>
        
        <span class="s0YkB">
          grant
        </span>
        
        <span class="sqjlB">
          ()
        </span>
      </code>
      
       / <code className="language-ts shiki shiki-themes github-light github-light material-theme-palenight" language="ts" style="">
        <span class="sqjlB">
          consent
        </span>
        
        <span class="sx-uw">
          .
        </span>
        
        <span class="s0YkB">
          revoke
        </span>
        
        <span class="sqjlB">
          ()
        </span>
      </code>
      
       / <code className="language-ts shiki shiki-themes github-light github-light material-theme-palenight" language="ts" style="">
        <span class="sqjlB">
          consent
        </span>
        
        <span class="sx-uw">
          .
        </span>
        
        <span class="s0YkB">
          hold
        </span>
        
        <span class="sqjlB">
          ()
        </span>
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Matomo
    </td>
    
    <td>
      <code>
        'required' | 'given' | 'not-required'
      </code>
    </td>
    
    <td>
      <code className="language-ts shiki shiki-themes github-light github-light material-theme-palenight" language="ts" style="">
        <span class="sqjlB">
          consent
        </span>
        
        <span class="sx-uw">
          .
        </span>
        
        <span class="s0YkB">
          give
        </span>
        
        <span class="sqjlB">
          ()
        </span>
      </code>
      
       / <code className="language-ts shiki shiki-themes github-light github-light material-theme-palenight" language="ts" style="">
        <span class="sqjlB">
          consent
        </span>
        
        <span class="sx-uw">
          .
        </span>
        
        <span class="s0YkB">
          forget
        </span>
        
        <span class="sqjlB">
          ()
        </span>
      </code>
      
       <em>
        (requires <code>
          defaultConsent: 'required'
        </code>
        
         or <code>
          'given'
        </code>
        
        )
      </em>
    </td>
  </tr>
  
  <tr>
    <td>
      Mixpanel
    </td>
    
    <td>
      <code>
        'opt-in' | 'opt-out'
      </code>
    </td>
    
    <td>
      <code className="language-ts shiki shiki-themes github-light github-light material-theme-palenight" language="ts" style="">
        <span class="sqjlB">
          consent
        </span>
        
        <span class="sx-uw">
          .
        </span>
        
        <span class="s0YkB">
          optIn
        </span>
        
        <span class="sqjlB">
          ()
        </span>
      </code>
      
       / <code className="language-ts shiki shiki-themes github-light github-light material-theme-palenight" language="ts" style="">
        <span class="sqjlB">
          consent
        </span>
        
        <span class="sx-uw">
          .
        </span>
        
        <span class="s0YkB">
          optOut
        </span>
        
        <span class="sqjlB">
          ()
        </span>
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      PostHog
    </td>
    
    <td>
      <code>
        'opt-in' | 'opt-out'
      </code>
    </td>
    
    <td>
      <code className="language-ts shiki shiki-themes github-light github-light material-theme-palenight" language="ts" style="">
        <span class="sqjlB">
          consent
        </span>
        
        <span class="sx-uw">
          .
        </span>
        
        <span class="s0YkB">
          optIn
        </span>
        
        <span class="sqjlB">
          ()
        </span>
      </code>
      
       / <code className="language-ts shiki shiki-themes github-light github-light material-theme-palenight" language="ts" style="">
        <span class="sqjlB">
          consent
        </span>
        
        <span class="sx-uw">
          .
        </span>
        
        <span class="s0YkB">
          optOut
        </span>
        
        <span class="sqjlB">
          ()
        </span>
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Clarity
    </td>
    
    <td>
      <code className="language-html shiki shiki-themes github-light github-light material-theme-palenight" language="html" style="">
        <span class="sqjlB">
          boolean | Record
        </span>
        
        <span class="sx-uw">
          <
        </span>
        
        <span class="sFfpx">
          string,
        </span>
        
        <span class="sg-iE">
          string
        </span>
        
        <span class="sx-uw">
          >
        </span>
      </code>
    </td>
    
    <td>
      <code className="language-ts shiki shiki-themes github-light github-light material-theme-palenight" language="ts" style="">
        <span class="sqjlB">
          consent
        </span>
        
        <span class="sx-uw">
          .
        </span>
        
        <span class="s0YkB">
          set
        </span>
        
        <span class="sqjlB">
          (value)
        </span>
      </code>
    </td>
  </tr>
</tbody>
</table>

See each script's registry page for notes on lossy projections and vendor caveats.

### Fanning out to multiple scripts

When one cookie banner drives several vendors, wire them explicitly in your accept handler. No magic, fully typed, no lossy remapping:

```ts
const ga = useScriptGoogleAnalytics({ id: 'G-XXX', defaultConsent: { ad_storage: 'denied', analytics_storage: 'denied' } })
const meta = useScriptMetaPixel({ id: '123', defaultConsent: 'denied' })
const matomo = useScriptMatomoAnalytics({ cloudId: 'foo.matomo.cloud', defaultConsent: 'required' })

function onAcceptAll() {
  ga.consent.update({
    ad_storage: 'granted',
    ad_user_data: 'granted',
    ad_personalization: 'granted',
    analytics_storage: 'granted',
  })
  meta.consent.grant()
  matomo.consent.give()
}

function onDeclineAll() {
  meta.consent.revoke()
  matomo.consent.forget()
}
```

### Granular categories

If users can toggle categories individually (analytics, marketing, functional), the same pattern applies; each script gets only the categories it understands:

```ts
function savePreferences(choices: { analytics: boolean, marketing: boolean }) {
  ga.consent.update({
    analytics_storage: choices.analytics ? 'granted' : 'denied',
    ad_storage: choices.marketing ? 'granted' : 'denied',
    ad_user_data: choices.marketing ? 'granted' : 'denied',
    ad_personalization: choices.marketing ? 'granted' : 'denied',
  })
  if (choices.marketing)
    meta.consent.grant()
  else meta.consent.revoke()
  if (choices.analytics)
    matomo.consent.give()
  else matomo.consent.forget()
}
```

## Third-party CMP recipes

When a dedicated Consent Management Platform owns the UI, bridge its events into each script's `consent` API.

### OneTrust

```ts
const ga = useScriptGoogleAnalytics({ id: 'G-XXX', defaultConsent: { ad_storage: 'denied', analytics_storage: 'denied' } })
const meta = useScriptMetaPixel({ id: '123', defaultConsent: 'denied' })

onNuxtReady(() => {
  function apply() {
    const groups = (window as any).OnetrustActiveGroups as string | undefined
    if (!groups)
      return
    const analytics = groups.includes('C0002')
    const marketing = groups.includes('C0004')
    ga.consent.update({
      analytics_storage: analytics ? 'granted' : 'denied',
      ad_storage: marketing ? 'granted' : 'denied',
      ad_user_data: marketing ? 'granted' : 'denied',
      ad_personalization: marketing ? 'granted' : 'denied',
    })
    if (marketing)
      meta.consent.grant()
    else meta.consent.revoke()
  }

  apply()
  window.addEventListener('OneTrustGroupsUpdated', apply)
})
```

### Cookiebot

```ts
const ga = useScriptGoogleAnalytics({ id: 'G-XXX', defaultConsent: { ad_storage: 'denied', analytics_storage: 'denied' } })
const meta = useScriptMetaPixel({ id: '123', defaultConsent: 'denied' })

onNuxtReady(() => {
  function apply() {
    const cb = (window as any).Cookiebot
    if (!cb?.consent)
      return
    ga.consent.update({
      analytics_storage: cb.consent.statistics ? 'granted' : 'denied',
      ad_storage: cb.consent.marketing ? 'granted' : 'denied',
      ad_user_data: cb.consent.marketing ? 'granted' : 'denied',
      ad_personalization: cb.consent.marketing ? 'granted' : 'denied',
    })
    if (cb.consent.marketing)
      meta.consent.grant()
    else meta.consent.revoke()
  }

  apply()
  window.addEventListener('CookiebotOnAccept', apply)
  window.addEventListener('CookiebotOnDecline', apply)
})
```
