Cookie Consent
Two complementary primitives
Nuxt Scripts ships two consent primitives that work together:
useScriptTriggerConsent(): a binary load gate. The script only starts loading after consent is granted.- Per-script
consentobject returned from every consent-awareuseScriptX(). A vendor-native, typed API for granting, revoking, or updating consent categories at runtime. Paired with each script'sdefaultConsentoption 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()
<script setup lang="ts">
import { scriptsConsent } from '#imports'
useScript('https://www.google-analytics.com/analytics.js', {
trigger: scriptsConsent,
})
</script>
<script setup lang="ts">
import { scriptsConsent } from '#imports'
</script>
<template>
<button @click="scriptsConsent.accept()">
Accept Cookies
</button>
</template>
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>
Delaying the load after consent
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.
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
| Script | defaultConsent | Runtime consent.* |
|---|---|---|
| Google Analytics | Partial<ConsentState> (GCMv2) | consent.update(state) |
| Google Tag Manager | Partial<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() |
| Clarity | boolean | 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)
})