X Enterprises
Composables

useXAFConsentTracking

Reads app.config.xAffiliate.tracking and dynamically injects GTM / GA4 / Clarity / custom scripts only after the matching consent category is granted. Used by <XAFCookieConsent> and the xaf-consent-tracking.client.ts plugin.

useXAFConsentTracking

The script-injection engine behind <XAFCookieConsent />. Reads app.config.xAffiliate.tracking and dynamically injects GTM / GA4 / Clarity / Meta Pixel / Hotjar / Segment / LinkedIn Insight / etc. only after the matching consent category is granted.

The composable is auto-imported in Nuxt and listens for consent:updated window events fired by the banner. It's also bootstrapped by the layer's xaf-consent-tracking.client.ts plugin, which calls rehydrate() on client mount so re-visitors get their tracking scripts loaded immediately — no flash of banner.

Renamed from useConsentTracking in the minor after v0.6.0 — the old name collided with nuxt-x-marketing's identically-named composable when a site extends both layers.

Most sites don't need to call this composable directly; the banner component does it for you. Reach for it when you want to programmatically grant consent or read the current state from elsewhere in your app.

Usage

const consent = useXAFConsentTracking()

// Programmatic consent
consent.acceptAll()
consent.rejectAll()
consent.setConsent({
  necessary: true,
  analytics: true,
  marketing: false,
  preferences: true,
})

// Read state
consent.hasConsent('analytics')   // boolean
consent.state.value               // reactive { necessary, analytics, marketing, preferences }
consent.hasDecision.value         // true once the visitor has saved any decision

// Manual hydration (called by the client plugin on mount)
consent.rehydrate()
consent.init()                    // rehydrate + register consent:updated listener

Returns

KeyTypeDescription
stateRef<{ necessary, analytics, marketing, preferences }>Reactive consent state. necessary is always true.
hasDecisionRef<boolean>true once the visitor has saved any decision (i.e. localStorage has the key).
setConsent(state)(ConsentState) => voidApply a full consent map, write to localStorage, fire scripts, and broadcast consent:updated.
acceptAll()() => ConsentStateGrant every category. Returns the new state.
rejectAll()() => ConsentStateGrant only necessary. Returns the new state.
rehydrate()() => voidRe-read localStorage and fire scripts for the granted categories. No event broadcast.
hasConsent(category)('necessary' | 'analytics' | 'marketing' | 'preferences') => booleanConvenience read of the current state.
init()() => voidBootstrap: rehydrate + register consent:updated listener. Idempotent — safe to call multiple times.

Configuration

The composable reads app.config.xAffiliate.tracking:

// app.config.ts
export default defineAppConfig({
  xAffiliate: {
    tracking: {
      // ---- Convenience IDs ----
      gtmId: 'GTM-XXXXXXX',
      ga4Id: 'G-XXXXXXXX',
      clarityId: 'abc123def4',

      // ---- Escape hatch: arbitrary scripts ----
      scripts: [
        {
          id: 'meta-pixel',
          src: 'https://connect.facebook.net/en_US/fbevents.js',
          category: 'marketing',
          attrs: { async: '' },
        },
        {
          id: 'meta-pixel-init',
          inline: 'fbq("init", "1234567890"); fbq("track", "PageView");',
          category: 'marketing',
        },
      ],

      // ---- Behavior ----
      autoInject: true, // default
    },
  },
})
KeyTypeDefaultDescription
gtmIdstringGoogle Tag Manager container ID. Fires the analytics category.
ga4IdstringGoogle Analytics 4 measurement ID. Skipped when gtmId is set (avoids double-counting).
clarityIdstringMicrosoft Clarity project ID. Skipped when gtmId is set.
scriptsTrackingScript[][]Arbitrary scripts (Meta Pixel, Hotjar, Segment, etc.). Each fires when its category is granted.
autoInjectbooleantrueWhen false, no scripts are injected (the composable still tracks state).

TrackingScript:

FieldTypeDescription
idstringStable ID — used to dedupe re-injection. Required.
srcstringExternal script URL.
inlinestringInline script body (no src).
category'necessary' | 'analytics' | 'marketing'Category gate. Default: 'analytics'.
attrsRecord<string, string>Extra attributes applied to the injected <script> (e.g. { async: '' }).

Dedupe and GTM-awareness

  • The composable dedupes by id — calling acceptAll() multiple times won't inject the same script twice.
  • When gtmId is set, GTM owns GA4 and Clarity. The composable skips the standalone gtag/js?id=G-XXX and Clarity snippets to avoid double-counting. Set gtmId alone if you want everything funneled through GTM.

Storage key

Defaults to xAffiliate.consent. Override via the banner's storageKey prop, or by writing/reading the key directly if you need to coordinate with another consent UI (e.g. the marketing layer's <XMarkPrivacyCookieConsent /> uses xMarketing.consent).

Events

EventPayloadFired when
consent:updated{ necessary, analytics, marketing, preferences }After setConsent (programmatic) or acceptAll / rejectAll.

The composable itself listens for consent:updated to keep state reactive and re-fire scripts when consent changes.


AI Context

composable: useXAFConsentTracking
package: "@xenterprises/nuxt-x-affiliate"
use-when: >
  Wiring consent-aware script injection for GTM / GA4 / Clarity / Meta Pixel /
  Hotjar / Segment / LinkedIn Insight / etc. The composable is auto-imported
  in Nuxt and listened to via the consent:updated window event. Most apps
  should drop tracking IDs into app.config.xAffiliate.tracking and let
  <XAFCookieConsent /> handle the rest — reach for the composable directly
  only for programmatic grant/reject flows or to read state.
storage-key: xAffiliate.consent
event: consent:updated
scripts-fire-on: matching category grant
Copyright © 2026