Webclat logoWebclat . | OneTrust Solutions
TCF & consent strings

Why is window.__tcfapi undefined, or how do I decode the euconsent-v2 TCF string client-side?

Short answer

window.__tcfapi is injected by the CMP's own stub script and follows a queue-command pattern rather than existing as a plain value on load, so checking for it synchronously before that stub has executed is the most common cause of it appearing undefined - call it as a command function instead, and decode the resulting euconsent-v2 string with the IAB's own reference decoding library, since it uses a custom bit-packed encoding, not base64-wrapped JSON.

Why this happens

The IAB Transparency & Consent Framework defines __tcfapi as a stub that a CMP injects early, designed around a command-queue pattern very similar to gtag/dataLayer - you call window.__tcfapi(command, version, callback, parameter), and it works whether or not the full CMP library has finished loading yet, because the stub queues calls until the real implementation is ready. Code that instead checks if (window.__tcfapi) synchronously on page load, before the stub script has had a chance to run, will see undefined even on a page that will have a working TCF API a moment later.

If the API is genuinely never injected at all, the more likely explanation is that the site isn't running a TCF-registered CMP, or the CMP is configured in a mode that doesn't enable IAB TCF support - not every CMP configuration ships the stub by default.

Separately, the euconsent-v2 string itself is not a convenience-encoded value - it's a custom bit-packed format defined by the IAB TCF v2 specification, which is why a hand-rolled base64/JSON parser breaks: there's no JSON inside it to parse, and the bit layout has changed across spec point-releases.

Fix it

  1. 1

    Never check window.__tcfapi synchronously - always call it as a queued function

    This is the fix for the vast majority of "undefined" reports - use the command pattern, which resolves correctly whether the stub or the full library is currently active.

    window.__tcfapi('addEventListener', 2, (tcData, success) => {
      if (success && tcData.eventStatus === 'tcloaded') {
        // tcData.tcString is available here
      }
    });
  2. 2

    Confirm your CMP is actually IAB TCF registered and configured to inject the stub

    Check your CMP's own admin settings for an IAB TCF / Global Vendor List integration toggle - some configurations run the banner and consent logic without ever enabling TCF support, in which case there is no stub to call regardless of timing.

  3. 3

    Use a real decoding library for the TC string, not a manual parser

    Decode with the IAB's own reference implementation (published under the iabtcf npm scope) rather than writing a bit-parser by hand - the encoding has changed across spec versions, and a hand-rolled decoder silently produces wrong values on a version mismatch rather than failing loudly.

  4. 4

    Read the string from the callback, not from a raw cookie value

    Pull tcData.tcString from the addEventListener/getTCData callback rather than reading whatever cookie your CMP happens to store it in - some CMPs wrap or namespace the raw string inside their own cookie format, which will fail if fed directly into a standard TCF decoder.

How to verify it worked

In the browser console, run window.__tcfapi('ping', 2, console.log) and confirm it returns a PingReturn-shaped object rather than throwing a TypeError.

Decode the tcString via the reference library and confirm the resulting purpose.consents object matches the choices actually made in the banner UI for a test session.

Test the ping call immediately on page load (not after a delay) to confirm the queue pattern resolves correctly even before the full CMP library has finished initializing.

Related

Still stuck, or want this checked against your specific setup?

We scope every engagement in discovery, before implementation - no assumptions about your stack.

Request a Free OneTrust Audit