---
title: "Fixing window.__tcfapi Undefined and Decoding the TCF String"
canonical_url: https://ot.webclat.com/qa/tcfapi-undefined-decode-tcf-string
description: "__tcfapi is a queued stub function, not a value that appears at once - checking it synchronously on load is the usual cause of "undefined", and the euconsent-v2 string needs a real decoder, not manual parsing."
source: Webclat | OneTrust Solutions (OneTrust partner, independent consultancy)
---

# 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. **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. **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. **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. **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

- [Why doesn't Google Consent Mode v2's ad_personalization/analytics_storage signal update the way I expect?](https://ot.webclat.com/qa/consent-mode-v2-signal-not-updating)
- [How do I make a visitor's cookie-consent choice persist across subdomains instead of re-prompting?](https://ot.webclat.com/qa/cookie-consent-persist-across-subdomains)
- [How do I fire a GTM trigger the instant a visitor updates their consent choice, without a page reload?](https://ot.webclat.com/qa/gtm-consent-update-trigger)

Contact: ot@webclat.com | (813) 694-4451 | https://ot.webclat.com/#contact
