If you run programmatic advertising, you've probably heard that you "need TCF 2.2." But hearing it and actually implementing it are different things. This guide skips the policy overview — you can read our TCF for Publishers primer for that — and goes straight to the mechanics: what the spec requires, how the pieces connect, and where implementations break.
We're writing for ad tech engineers, publisher dev teams, and anyone who's stared at a TC String and wanted to understand what's actually inside it.
The Four Moving Parts of a TCF 2.2 Implementation
Every TCF implementation has four components that must work together:
- The CMP (Consent Management Platform) — displays the consent UI, collects user choices, and encodes them into a TC String. Must be registered with IAB Europe and assigned a CMP ID.
- The TC String — a base64url-encoded binary string that carries the user's consent and legitimate-interest choices, the list of vendors disclosed, and any publisher restrictions. Every ad call on the page includes this string.
- The CMP API (
__tcfapi) — a JavaScript stub loaded before any vendor tags. Vendors call it to check consent status before processing data. - The Global Vendor List (GVL) — an IAB-maintained JSON registry of every vendor in the framework, including which purposes and legal bases each vendor declares. Your CMP uses it to populate the consent UI and validate vendor claims.
If any of these four pieces is misconfigured, the chain breaks — and the failure mode is usually silent: vendors either can't read consent or read it incorrectly, resulting in lost ad revenue or compliance violations.
TC String Structure and Encoding
The TC String isn't opaque magic — it's a well-defined binary format packed into base64url encoding. Understanding its structure helps enormously when debugging. A TC String contains multiple segments, separated by periods (.):
- Core segment (always present) — TCF version, creation/update timestamps, CMP ID, CMP version, consent screen number, consent language, vendor list version, policy version, publisher country code, purpose consents (a 24-bit bitfield), purpose legitimate interests, vendor consents (variable-length bitfield or range encoding), and vendor legitimate interests.
- DisclosedVendors segment (mandatory since TCF 2.3, March 2026) — lists all vendors the CMP actually showed to the user, regardless of whether they received consent.
- PublisherTC segment (optional) — carries publisher-specific purpose consents and custom purposes, separate from vendor-level consent.
Bitfield vs. Range Encoding
Vendor consent uses either a bitfield (one bit per vendor ID, up to the max vendor ID) or range encoding (start/end pairs for consecutive vendor IDs). The CMP chooses whichever produces a shorter string. Decoders must handle both. The MaxVendorId field tells you the highest vendor ID in the bitfield, and the IsRangeEncoding bit indicates which encoding is used.
Here's how purpose consent is encoded in the core segment. Each of the 24 TCF purposes gets a single bit:
Bit position: 1 2 3 4 5 6 7 8 9 10 ... 24
Purpose: Store Basic Ads Profile Measure ...
Consent: 1 1 0 1 0 1 0 0 0 0 0
In this example:
- Purpose 1 (Store/access on device): CONSENT
- Purpose 2 (Basic ad selection): CONSENT
- Purpose 3 (Personalised ad profiles): NO CONSENT
- Purpose 4 (Personalised ads): CONSENT
- Purpose 5 (Content profiles): NO CONSENT
- Purpose 6 (Personalised content): CONSENTYou don't need to decode this manually in production — the CMP API does it for you — but understanding the structure matters when you're troubleshooting why a specific vendor thinks it doesn't have consent.
The CMP API: __tcfapi
The __tcfapi function is the interface between your CMP and every vendor tag on the page. Per the TCF spec, a CMP must define window.__tcfapi as a synchronous function before any vendor scripts load. The CMP stub must also create window.__tcfapiLocator (an iframe used for cross-domain access via postMessage).
The API signature is:
__tcfapi(command, version, callback, parameter)
// command: string — the API command
// version: number — TCF version (2 for TCF 2.0/2.2)
// callback: function(result, success) — called with the result
// parameter: optional — command-specific argumentKey Commands
getTCData — returns the current TC data (consent string, purpose consents, vendor consents, etc.). Vendors call this to check whether they have consent.
1 __tcfapi('getTCData', 2, function(tcData, success) { 2 if (success) { 3 console.log('TC String:', tcData.tcString); 4 console.log('Purpose 1 consent:', tcData.purpose.consents[1]); 5 console.log('Vendor 755 consent:', tcData.vendor.consents[755]); 6 console.log('GDPR applies:', tcData.gdprApplies); 7 } 8 });
addEventListener — like getTCData, but fires again whenever consent state changes (e.g., when the user updates their preferences). This is what well-behaved vendor tags should use instead of a one-shot getTCData.
1 __tcfapi('addEventListener', 2, function(tcData, success) { 2 if (success && tcData.eventStatus === 'tcloaded') { 3 // Consent has been loaded (returning user) 4 initializeVendorTag(tcData); 5 } else if (success && tcData.eventStatus === 'useractioncomplete') { 6 // User just made a choice on the banner 7 initializeVendorTag(tcData); 8 } 9 });
ping — a quick check to see if the CMP is loaded and what state it's in. Returns cmpLoaded, cmpStatus, displayStatus, and gdprApplies. Useful for diagnostics.
1 __tcfapi('ping', 2, function(pingReturn) { 2 console.log('CMP loaded:', pingReturn.cmpLoaded); 3 console.log('CMP status:', pingReturn.cmpStatus); // 'stub' | 'loaded' | 'error' 4 console.log('Display status:', pingReturn.displayStatus); // 'visible' | 'hidden' | 'disabled' 5 console.log('GDPR applies:', pingReturn.gdprApplies); 6 console.log('CMP ID:', pingReturn.cmpId); 7 });
Global Vendor List (GVL) Management
The GVL is a JSON file published by IAB Europe listing every registered TCF vendor, their declared purposes, legal bases, and data retention policies. Your CMP fetches it to populate the consent UI with accurate vendor information.
Where to Fetch the GVL
The canonical URL is https://vendor-list.consensu.org/v3/vendor-list.json for the latest version. Specific versions are at https://vendor-list.consensu.org/v3/archives/vendor-list-v{version}.json. The GVL updates roughly weekly.
What the GVL Contains
Each vendor entry includes: id, name, purposes (consent-based), legIntPurposes (legitimate interest), flexiblePurposes (either legal basis), specialPurposes (no consent required), features/specialFeatures, policyUrl, dataRetention, and localized urls for privacy policies.
GVL Caching: Don't Hit the CDN on Every Pageview
Fetching the full GVL on every page load adds ~300KB of JSON and a blocking network request. Cache the GVL locally and check for updates no more than once per day. Store the vendorListVersion you fetched and compare it against the latest endpoint. Most CMPs (including CookieBeam) handle this caching internally — but if you're building custom integrations or debugging, be aware that stale GVL data can cause your consent UI to show outdated vendor information.
Publisher Restrictions: Controlling Vendor Behavior
Publisher restrictions are one of the most powerful — and most underused — features of TCF 2.2. They let you, as a publisher, override a vendor's declared legal basis for specific purposes.
Why This Matters
Under TCF 2.2, vendors can declare legitimate interest as their legal basis for certain purposes. But as a publisher, you might decide that legitimate interest isn't appropriate for your audience or jurisdiction. Publisher restrictions let you enforce stricter rules:
- Restriction Type 0: Purpose flatly not allowed — the vendor cannot process data for this purpose at all, regardless of what they declared in the GVL.
- Restriction Type 1: Require consent — even if the vendor declared legitimate interest, they must have explicit user consent for this purpose.
- Restriction Type 2: Require legitimate interest — the opposite: force the vendor to rely on legitimate interest instead of consent (rarely used).
Practical Example
Say you want to require consent for Purpose 4 (Create a personalised ads profile) for all vendors, even those claiming legitimate interest. Your CMP would encode this as a publisher restriction in the TC String:
1 { 2 "publisherRestrictions": [ 3 { 4 "purposeId": 4, 5 "restrictionType": 1, 6 "vendorIds": [] 7 } 8 ] 9 }
An empty vendorIds array means the restriction applies to all vendors. You can also target specific vendor IDs if you only want to restrict certain partners.
In practice, most publishers should at minimum require consent for Purposes 3 through 6 (ad profiling, personalised ads, content profiling, personalised content). Some DPAs — particularly in France and Germany — have taken the position that legitimate interest is not a valid basis for advertising purposes under GDPR, and regulatory enforcement has consistently backed this up.
Step-by-Step: Implementing TCF 2.2 on Your Site
Whether you're using an off-the-shelf CMP or validating an existing setup, here's what needs to happen in order:
TCF 2.2 Implementation Steps
1. Choose a registered CMP
Your CMP must be registered with IAB Europe and have an assigned CMP ID. Only registered CMPs can generate valid TC Strings. The IAB Europe CMP List shows all registered CMPs. If you're evaluating CMPs, verify their registration status before going further. An unregistered CMP produces TC Strings that vendors will reject.
2. Load the CMP stub before everything else
The __tcfapi stub must be the first script in your <head>. It needs to be present before any vendor tags attempt to call it. If a vendor tag fires before the stub exists, it may assume GDPR doesn't apply (failing open) or that consent was denied (failing closed — killing your ad revenue).
3. Configure your vendor list
Decide which GVL vendors you actually use. Don't disclose all 1,200+ vendors to your users — that's terrible UX and, under TCF 2.2's transparency requirements, arguably non-compliant. List only the vendors you genuinely work with. Your CMP should let you select specific vendors from the GVL.
4. Set purpose legal bases and publisher restrictions
For each purpose, decide whether you'll accept consent, legitimate interest, or consent only. Apply publisher restrictions for any purpose where you want to override vendor-declared bases. At minimum, require consent for Purposes 3-6 (advertising-related purposes).
5. Configure the consent UI
TCF 2.2 requires that the consent UI clearly present all purposes and vendors, provide granular control (per-purpose and per-vendor), and offer an easy way to withdraw consent. The first layer should describe purposes in plain language. The second layer should show the full vendor list with their declared purposes and privacy policies.
6. Test TC String generation
After a user makes a choice, verify the TC String is generated correctly. Use the IAB's TC String decoder tools or CookieBeam's built-in diagnostics to inspect the string. Check that vendor consents, purpose consents, publisher restrictions, and the disclosedVendors segment are all present and accurate.
7. Verify vendor integration
Confirm that vendor tags are actually calling __tcfapi and respecting the result. Open browser DevTools, filter network requests, and check that ad calls include the gdpr and gdpr_consent parameters. If a vendor fires before checking consent, that's a compliance violation on your site.
8. Wire up Google Consent Mode alongside TCF
TCF and Google Consent Mode are complementary — see the section below. Configure your CMP to emit both the TC String (for programmatic vendors) and Consent Mode signals (for Google tags). Don't treat them as either/or.
CMP Registration with IAB Europe
Publishers don't register as CMPs — they use a registered CMP. But if you're building your own consent solution, here's what CMP registration involves:
- Application — submit to IAB Europe with documentation of your CMP's TC String generation,
__tcfapibehavior, and vendor list management. - Compliance check — IAB Europe reviews your implementation against the TCF spec (TC String correctness, API behavior, UI requirements).
- CMP ID assignment — on approval, you receive a unique numeric ID embedded in every TC String your CMP generates.
- Ongoing obligations — update when the spec changes, respond to compliance complaints, maintain accurate records.
For most publishers: verify your CMP is on the IAB Europe CMP List. If it isn't, switch — your TC Strings are invalid otherwise.
Integration with Google Consent Mode
This is the question we get most: "Do I need TCF and Consent Mode, or just one?" The short answer: you probably need both. For the full comparison, see our TCF 2.2 vs Google Consent Mode v2 guide. Here we'll focus on the practical wiring.
What Each One Does
TCF produces a TC String that programmatic ad vendors (SSPs, DSPs, ad exchanges) read to determine consent. Google Consent Mode sets gtag consent states (ad_storage, analytics_storage, ad_user_data, ad_personalization) that Google tags read. They're different signal formats for different consumers.
How to Wire Both from One CMP
Your CMP should translate the user's consent choices into both formats simultaneously. When a user grants consent for TCF Purpose 1 (Store/access on device) and Purpose 3-4 (ad profiling/personalised ads), the CMP should also set the Google Consent Mode signals accordingly:
1 // Default state: deny everything until user consents 2 gtag('consent', 'default', { 3 ad_storage: 'denied', 4 analytics_storage: 'denied', 5 ad_user_data: 'denied', 6 ad_personalization: 'denied', 7 wait_for_update: 500 // milliseconds to wait for CMP 8 }); 9 10 // After CMP collects consent, update based on TCF choices: 11 __tcfapi('addEventListener', 2, function(tcData, success) { 12 if (!success) return; 13 if (tcData.eventStatus === 'tcloaded' || tcData.eventStatus === 'useractioncomplete') { 14 gtag('consent', 'update', { 15 ad_storage: tcData.purpose.consents[1] ? 'granted' : 'denied', 16 analytics_storage: tcData.purpose.consents[1] ? 'granted' : 'denied', 17 ad_user_data: tcData.purpose.consents[3] ? 'granted' : 'denied', 18 ad_personalization: tcData.purpose.consents[4] ? 'granted' : 'denied' 19 }); 20 } 21 });
In practice, a CMP like CookieBeam handles this mapping internally — you don't write this code yourself. But knowing how it works helps you verify the implementation is correct and debug issues where Consent Mode signals don't match TC String values.
Common Implementation Mistakes and Debugging
After reviewing hundreds of TCF implementations, these are the errors we see most often:
1. CMP stub loads too late
If vendor scripts execute before __tcfapi exists, they can't check consent. Some vendors fail open (process data without consent — a GDPR violation). Others fail closed (no ads load — revenue loss). The fix: make your CMP the very first script in <head>, before Google Tag Manager, before ad tags, before anything.
1 <!-- CORRECT: CMP stub loads first --> 2 <head> 3 <script src="https://cdn.cookiebeam.com/banner/YOUR_ID/latest.js"></script> 4 <script src="https://www.googletagmanager.com/gtm.js?id=GTM-XXXX"></script> 5 </head> 6 7 <!-- WRONG: GTM loads before CMP --> 8 <head> 9 <script src="https://www.googletagmanager.com/gtm.js?id=GTM-XXXX"></script> 10 <script src="https://cdn.cookiebeam.com/banner/YOUR_ID/latest.js"></script> 11 </head>
2. Stale GVL version
Your CMP shows vendors based on the GVL it has cached. If a vendor updates their declared purposes in a new GVL version and your CMP is using a cached copy from weeks ago, you're showing users outdated information. Verify your CMP updates its GVL cache at least daily.
3. Missing DisclosedVendors segment
Since TCF 2.3 took effect in March 2026, TC Strings must include the DisclosedVendors segment — the list of vendors that were actually presented to the user. Strings without this segment are now treated as invalid by compliant vendors. If your CMP hasn't been updated, you're silently losing ad demand because vendors see an invalid consent signal.
4. No publisher restrictions on advertising purposes
By default, many CMPs let vendors use legitimate interest for advertising purposes. In the EEA, DPAs have repeatedly ruled this isn't appropriate. If you haven't explicitly set publisher restrictions requiring consent for Purposes 3-6, your implementation may be technically functional but legally non-compliant.
5. TC String not passed in ad requests
Just having a TC String on the page isn't enough — it must be included in bid requests. Prebid.js handles this automatically through its gdpr module, but custom integrations or direct ad server calls may not. Check your ad calls in the Network tab:
// In a Prebid.js bid request, look for:
bidRequest.gdprConsent.consentString → should contain the TC String
bidRequest.gdprConsent.gdprApplies → should be true for EEA visitors
// In a direct ad server call, look for query parameters:
?gdpr=1&gdpr_consent=CQHzD...base64string...6. Consent Mode and TCF out of sync
If your CMP sets TCF consent but doesn't update Google Consent Mode (or vice versa), Google tags and programmatic vendors will have contradictory information about the same user. This leads to data discrepancies in GA4 reporting and potential compliance issues. Verify both signals fire from the same consent event.
Debugging Toolkit
When things go wrong, these tools help:
- Browser DevTools → Console: Run
__tcfapi('ping', 2, console.log)and__tcfapi('getTCData', 2, console.log)to inspect live state. - IAB TC String decoder: Paste a TC String into a decoder to verify its contents match what you expect.
- Network tab filtering: Search for
gdpr_consentorconsentin query parameters to verify the string is being passed in ad calls. - CookieBeam diagnostics: CookieBeam's dashboard shows TC String generation status, vendor list sync state, and consent mode signal mapping — all in one view.
How CookieBeam Handles TCF 2.2 Automatically
CookieBeam includes a full TCF 2.2 implementation that handles the spec's complexity for you. Here's what that means concretely:
- Automatic GVL sync — CookieBeam fetches and caches the latest Global Vendor List daily. When IAB Europe publishes a new version, your consent UI updates automatically. No manual intervention needed.
- TC String generation — every consent interaction produces a spec-compliant TC String including the core segment, DisclosedVendors (TCF 2.3 compliant), and publisher restrictions. The string is stored in the
euconsent-v2cookie and made available through the__tcfapiendpoint. - Publisher restrictions UI — configure restrictions per-purpose and per-vendor directly in the CookieBeam dashboard. No need to manually encode restriction segments.
- Unified Consent Mode + TCF — a single banner drives both the TC String and Google Consent Mode signals. When a user consents to TCF Purpose 1, CookieBeam simultaneously grants
ad_storageandanalytics_storage. Purpose 3 maps toad_user_data, Purpose 4 toad_personalization. You don't configure this mapping — it's built in. - Script blocking — CookieBeam blocks vendor scripts until consent is confirmed, so there's no race condition between your CMP stub and vendor tags. Tags fire only after the TC String confirms the required purpose consent.
- Audit trail — every consent event is logged server-side with the full TC String, timestamp, and user choices. This record satisfies GDPR's accountability requirement if a DPA asks you to demonstrate proof of consent.
Ready to Implement?
Getting TCF 2.2 right is mostly about getting the details right: load order, vendor list hygiene, publisher restrictions, and keeping TCF and Consent Mode in sync. A TCF-capable CMP like CookieBeam takes care of the encoding, the API, and the GVL management — you configure your vendor list and restrictions, and the rest happens automatically. Start with the TCF overview if you need the policy context, or check our TCF vs Consent Mode comparison if you're still sorting out which framework does what.