Skip to main content
Back to Guides
Compliance5 min read

Managing the TCF Global Vendor List (GVL)

The Global Vendor List is a large JSON document that changes weekly. Here's how to load it without slowing every page, why the slim and full variants exist, and how the version field ties back to the TC String.

What the GVL Is, and Why It's Big

The Global Vendor List is the registry every TCF CMP encodes against. It's a single JSON document, hosted by IAB Europe, that enumerates every registered vendor along with the purposes, special purposes, features, special features, and legal bases each one declares. The full list runs well over a megabyte because it includes human-readable vendor names, descriptions, and policy URLs for hundreds of companies.

That size is the core engineering problem. You need the vendor data to encode a TC String and to render a vendor panel, but you can't afford to ship a megabyte of JSON on every page load just in case a user opens that panel. How you split the difference decides your consent banner's effect on page speed.

The v3 Structure

Since TCF 2.2, the list is served as GVL v3. The document's top-level metadata is what you read first:

  • gvlSpecificationVersion: the version of the GVL specification the file follows.
  • vendorListVersion: the incrementing version number of this particular list. This is the number your TC String records.
  • tcfPolicyVersion: the TCF policy version the list adheres to.
  • lastUpdated: the timestamp of the most recent change.

Below that sit the purposes, specialPurposes, features, and specialFeatures dictionaries, the vendors map keyed by vendor id, and, new in v3, a dataCategories dictionary that enumerates the categories of data a vendor may process. Each vendor entry lists the purpose ids it relies on, which it claims under legitimate interest, its special purposes, features, and flexible purposes.

Weekly Publishing Means the Version Moves

IAB republishes the list weekly at vendor-list.consensu.org/v3/vendor-list.json as vendors update their registrations. New vendors appear, existing ones change their declared purposes, and the vendorListVersion ticks up. A CMP that fetched the list once and cached it forever will, within weeks, be encoding against a list that no longer describes reality: it can miss newly registered vendors entirely and misrepresent ones that changed their declarations.

So GVL freshness is an ongoing job. You want a cache that's fast on the hot path but refreshes on a sensible cadence, and you never want to hardcode a version number and forget it.

Slim and Full: Two Loads for Two Jobs

The trick that makes this tractable is recognizing that encoding a TC String and displaying vendor details need different data. Encoding needs only vendor ids and their purpose and legal-basis mappings, a few tens of kilobytes. Displaying the vendor panel needs the names, descriptions, and policy links, the part that makes the file large.

CookieBeam's runtime splits the load along exactly this line. On every page it fetches a slim vendor list, roughly 50 KB, which carries the ids and purpose mappings needed to build the consent string. The full list, around 1.5 MB, is fetched only when a user actually opens the vendor detail panel. Most visitors never open it, so most visitors never pay for it. Both results are cached in memory so a second call returns the same promise rather than refetching.

Caching and a Fallback Path

A single origin for a file this important is a single point of failure, so a resilient loader tries more than one source. CookieBeam's GVL loader fetches from a mirror on its own object storage first, then falls back to the IAB CDN at the vendor-list.consensu.org/v3 endpoint if the mirror misses. The mirror keeps the common case fast and close, while the IAB CDN guarantees you can always reach an authoritative copy.

In-memory caching handles the within-session case: the first loadSlim stores the parsed object and every later call returns it directly. Because the loader caches the in-flight promise rather than only the resolved value, ten vendor scripts asking for the list during the same page load trigger one fetch, not ten. When a fetch fails, the loader clears the cached promise so the next attempt can retry rather than replaying a rejected result.

The Version in the List Must Match the String

Here's the link that ties GVL management back to compliance. The vendorListVersion from the list you loaded gets written into every TC String you encode. A validator reading that string can look up the version and confirm the vendor ids you included actually existed in it. If you encode against a fresh list but stamp an old version, or vice versa, the string is internally inconsistent and vendors are right to distrust it.

This is why you don't manage the GVL as an afterthought. The list you load, the version you record, and the vendor ids you set have to describe the same snapshot. For the full picture of what makes a string pass, see the TCF CMP validation guide.

Pitfalls

  • Shipping the full list on every page. A megabyte of vendor JSON in the critical path is a Core Web Vitals problem you don't need. Load slim, fetch full on demand.
  • Never refreshing. A months-old cache encodes against a list that's moved on. Refresh on a schedule.
  • One source, no fallback. If your only origin has a bad day, your CMP can't encode. Mirror, then fall back to the IAB CDN.
  • Refetching per vendor call. Cache the promise so concurrent callers share one request.
  • Hardcoding a version. The version has to come from the list you actually loaded.
Managing the TCF Global Vendor List (GVL) | CookieBeam | CookieBeam