Skip to main content
Back to Guides
Compliance5 min read

The TCF and GPP Stub: Async Loading Done Right

Vendor scripts call the CMP API before the CMP finishes loading. The stub is what keeps those early calls from throwing. Here's how the stub, the locator iframe, and the command queue work together.

Why a Stub Exists At All

A CMP is a fair amount of JavaScript, and you load it asynchronously so it doesn't block rendering. But ad libraries, prebid, and Google's tags all want to call __tcfapi or __gpp the moment they run, which is often before your CMP has finished loading. If the function doesn't exist yet, those calls throw and the vendor either fails or assumes no CMP is present. The stub is the small piece of code that defines the API function immediately, answers what it can, and buffers the rest so nothing is lost in the gap.

Both frameworks use the same pattern. The TCF has a __tcfapi stub; the GPP has a __gpp stub. The details differ, but the job is identical: be present and correct before the real CMP arrives.

The Locator Iframe

A vendor's script might be running inside a nested iframe, where it can't see the top frame's __tcfapi function directly. The stub solves cross-frame discovery by creating a hidden iframe with a specific name, __tcfapiLocator for TCF and __gppLocator for GPP. A script in a child frame walks up its parent chain looking for a frame that contains that named locator; finding it is how it knows a CMP lives somewhere above and that it should talk to the API by postMessage instead of a direct call.

Creating the iframe has one ordering wrinkle: it needs document.body to exist. A well-written stub checks for the body and, if it isn't ready, waits for DOMContentLoaded and retries, rather than throwing during head execution. CookieBeam's GPP stub does exactly this, retrying the locator creation once the body is available.

ping Answers Immediately; Everything Else Waits

Not every command can be answered by a stub, because the stub doesn't have the user's consent data yet. The one command it must answer synchronously is ping. A caller uses ping to ask whether the real CMP has loaded and, for TCF, whether GDPR applies. Because ping returns a status object without needing consent data, the stub can answer it truthfully: cmpStatus is "stub", signal is not ready.

That single honest answer is enough for a well-behaved vendor to decide to wait. It sees the stub status, registers a listener or retries later, and doesn't try to act on consent that doesn't exist yet. The GPP stub extends the same idea to the other generic commands: it answers ping, addEventListener, and removeEventListener directly, and returns safe defaults for hasSection, getSection, and getField (no section, null field) until the real CMP takes over.

Everything Else Gets Queued

Commands the stub can't answer aren't dropped, they're enqueued. The stub pushes each call, arguments and callback together, onto a queue. When the full CMP script loads, it reads that queue, redefines the global function with its real implementation, and replays the buffered calls in first-in-first-out order against the real API. Every early caller eventually gets its callback, just slightly later than it asked.

FIFO order matters because some calls depend on the order they were made. Replaying them in the sequence they arrived preserves the semantics a caller expected. This is the mechanism that makes async loading safe: nothing a vendor called during the loading window is lost, it's deferred and then honored.

The postMessage Bridge

Direct function calls only work within the same frame. For the cross-frame case the locator iframe advertises, the stub also installs a message event handler. A child frame posts a structured message (for TCF, an object with a __tcfapiCall property carrying the command, version, callId, and parameter); the stub's handler unpacks it, invokes the local API function, and posts the result back to the source frame as a __tcfapiReturn message tagged with the same callId. The GPP stub mirrors this with __gppCall and __gppReturn.

The callId is what lets a frame match a response to the request it made, since several calls can be in flight at once. Without it, a frame couldn't tell which answer belonged to which question.

Put It in the Head, First

All of this only works if the stub runs before any script that might call the API. That means inlining the stub in the document head, ahead of your ad tags, prebid, and Google libraries. If a vendor script executes above the stub in source order, its early call still hits an undefined function and throws, which is precisely the failure the stub exists to prevent.

Inlining also matters for timing: an external stub file is subject to network latency, and any delay widens the window where the API is undefined. A few hundred bytes of inline JavaScript in the head closes that window to essentially zero.

How CookieBeam Generates Both Stubs

CookieBeam ships a stub for each framework. Its TCF stub is the standard IAB snippet, inlined into the head ahead of other scripts, defining __tcfapi, creating the __tcfapiLocator iframe, handling ping and setGdprApplies, queuing everything else, and running the postMessage bridge. Its GPP stub is generated code built from the CMP's supported-API list, so the stub's advertised supportedAPIs always matches what the full runtime will actually encode.

Both are designed to be replaced cleanly: when the real CookieBeam runtime loads, it takes over the global function and replays the queue, so the handoff is invisible to vendors. For what the full runtime does once it's live, see the GPP __gpp() API guide and the TCF CMP validation guide.

The TCF and GPP Stub: Async Loading Done Right | CookieBeam | CookieBeam