Guides

Cookie Consent

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.

export const scriptsConsent = useScriptTriggerConsent()

Reactive source

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

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.

<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>
const consent = useScriptTriggerConsent({
  consent: agreedToCookies,
  postConsentTrigger: () => new Promise<void>(resolve =>
    setTimeout(resolve, 3000),
  ),
})

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.

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

ScriptdefaultConsentRuntime consent.*
Google AnalyticsPartial<ConsentState> (GCMv2)consent.update(state)
Google Tag ManagerPartial<ConsentState> (GCMv2)consent.update(state)
Bing UET{ ad_storage }consent.update({ ad_storage })
Meta Pixel'granted' | 'denied'consent.grant() / consent.revoke()
TikTok Pixel'granted' | 'denied' | 'hold'consent.grant() / consent.revoke() / consent.hold()
Matomo'required' | 'given' | 'not-required'consent.give() / consent.forget() (requires defaultConsent: 'required' or 'given')
Mixpanel'opt-in' | 'opt-out'consent.optIn() / consent.optOut()
PostHog'opt-in' | 'opt-out'consent.optIn() / consent.optOut()
Clarityboolean | Record<string, string>consent.set(value)

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:

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:

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

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

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)
})