Skip to content

Meta Pixel: consent.grant() before fbevents loads never takes effect with defaultConsent: 'denied' #928

Description

@felixgabler

🐛 The bug

Hi, and thanks for Nuxt Scripts! We use the Meta Pixel registry with defaultConsent: 'denied' and call consent.grant() once our cookie banner knows the visitor has agreed. When that grant happens before fbevents.js has loaded (for example, the decision is already stored in a cookie), the pixel never sends anything: no config request and no /tr.

From what we can see, clientInit queues ['consent', 'revoke'] ahead of init and PageView, and a grant made before load is appended to the same queue:

['consent', 'revoke'], ['init', id], ['track', 'PageView'], ['consent', 'grant']

When fbevents loads, it replays that queue in order and stops at the revoke. It only lets consent commands skip its lock when they arrive live through fbq.callMethod, so the queued grant behind the revoke is never reached. Any fbq('consent', 'grant') made after load does unlock it, which is why an in-session "Accept" click appears to work.

We might be missing an intended usage here, so happy to be corrected.

🛠️ To reproduce

https://stackblitz.com/edit/nuxt-starter-j6imudo3?file=pages%2Findex.vue

Open the preview and it prints the result: the fbq.queue contents and whether a PageView request was sent.

The page is the snippet below on @nuxt/scripts 1.3.9 and Nuxt 4.5.2. It uses scriptOptions: { bundle: false } only because WebContainers can't fetch fbevents.js at build time.

<script setup lang="ts">
const { consent } = useScriptMetaPixel({
  id: '1234567890',
  defaultConsent: 'denied',
  scriptOptions: { bundle: false },
})
// Consent already known, e.g. from a cookie, before the pixel has loaded.
if (import.meta.client)
  consent?.grant()
</script>

Result: fbevents 2.9.408 loads, fbq.queue stays at ["init,1234567890", "track,PageView", "consent,grant"], and no /tr request is made.

We see the same thing locally against main (4bb52cc) in the playground. Tracing window.fbq.queue in Chrome there:

  • before fbevents loads: consent,revoke / init / track,PageView / consent,grant
  • about 100 ms later, fbevents sets callMethod
  • after load: the queue is stuck at init / track,PageView / consent,grant, and every later event is appended behind it
  • no request to signals/config or /tr

🌈 Expected behavior

A grant made before the pixel loads applies before init, the same as if defaultConsent had been 'granted': the config request and the PageView go out, and later events are sent.

The docs describe this exact flow under Consent Mode (defaultConsent: 'denied', then consent.grant()).

ℹ️ Additional context

  • Seen on 1.3.9 and on main (4bb52cc); the stub is the same in both.
  • The same page on the StackBlitz starter's default @nuxt/scripts 0.10.5 does send the PageView. As far as we can tell, that is only because defaultConsent and the consent API don't exist there yet, so no revoke is queued. They first appear in 1.0.0-rc.10 (from feat: vendor-native consent controls for consent-aware scripts #712), and the stub has queued the revoke this way since then.
  • A revoke made before load has the mirror problem: it is appended after the queued PageView, so that PageView is still sent once the pixel loads.
  • We have a small fix with tests that we would be glad to open as a PR: before fbevents loads, a consent call replaces the pending consent state at the head of the queue instead of being appended. With it, the same repro sends the config request, the PageView and later events.

I investigated this with help from an AI coding assistant (Claude Code), which also drafted this text. I reviewed it, and the results above come from real runs.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions