Prebid.js 11.x Production Blueprint: Price Floors Schema 2, TCF Control, Activity Controls, and Strict StorageControl
In Prebid.js 11.0, any bid adapter, user ID submodule, or real-time data (RTD) provider that attempts to read localStorage or document.cookie without a

In Prebid.js 11.0, any bid adapter, user ID submodule, or real-time data (RTD) provider that attempts to read localStorage or document.cookie without a declared Global Vendor List (GVL) ID or an explicit storageControl exemption is terminated at the wrapper core before a network socket ever opens. For publishers migrating from Prebid 8.x or 9.x architectures through 10.x and into the 11.x branch, treating a major version upgrade as a routine binary swap introduces silent revenue degradation. Unvalidated multiformat bid responses get dropped at the auction gate, legacy video pod modules fail to compile during custom bundle builds, and unconfigured cross-origin credential policies strip third-party cookies from high-value demand requests.
Modern client-side header bidding operates under compressed browser execution budgets and stringent regulatory oversight. When a publisher page initializes Google Publisher Tag (GPT) alongside a Prebid.js bundle, the wrapper must resolve user consent via the IAB Transparency and Consent Framework (TCF 2.2) or Global Privacy Platform (GPP), enrich first-party identity graphs, fetch or compute dynamic price floors, execute parallel client-side and Prebid Server auctions, and inject key-value targeting into Google Ad Manager (GAM 360) within a 400-to-800-millisecond window. Every millisecond wasted on deprecated submodules or unthrottled DOM queries directly degrades viewability and clearing prices.
At Sky Boosts, our engineering teams architect and maintain enterprise header bidding deployments across web, mobile app, and Connected TV (CTV) environments. By unifying client-side Prebid.js 11.x, Prebid Server (Go and Java), OpenRTB 2.6, VAST 4.3, and GAM / AdX Multiple Customer Management (MCM) within our monetization platform, we protect publisher yield across 100% Google-friendly, HUMAN and Pixalate IVT-clean inventory backed by reliable Net-30 and Net-60 payment terms. This blueprint details the exact engineering specifications required to deploy Prebid.js 11.x in production, spanning core breaking changes, Price Floors Schema 2 experimentation, and deterministic privacy enforcement via Activity Controls.
Upgrading to Prebid.js 10.x and 11.x: Critical Breaking Changes
The architectural trajectory from Prebid.js 9.x through 10.x and into 11.x reflects a deliberate hardening of the client-side auction engine. Historically, Prebid.js tolerated loose type matching, implicit storage access, and experimental browser API polyfills to maximize backward compatibility across disparate demand adapters. Over time, that permissiveness created two major production liabilities: main-thread JavaScript bloat that hurt Core Web Vitals (specifically Interaction to Next Paint, or INP) and compliance ambiguity when regulators audited client-side cookie access and cross-origin network calls.
According to the official Prebid.js 11.0 release notes, version 11.0 removes obsolete modules (paapi, adpod, dfpAdServerVideo, and gamAdpod), rejects bids whose returned mediaType does not match the requested adUnit.mediaTypes object by default, defaults the storageControl module to strict enforcement, and introduces customGptSlotMatching for deterministic ad server binding. Engineering teams planning an upgrade must audit their build manifests, ad unit declarations, and GPT integration scripts against each of these four behavioral shifts before routing production traffic through an 11.x binary.
Removal of Legacy AdPod, PAAPI, and dfpAdServerVideo Modules
When compiling a custom Prebid.js 11.x bundle via Gulp (gulp build --modules=...), build pipelines that reference paapi, adpod, dfpAdServerVideo, or gamAdpod will throw fatal module resolution errors or silently omit the expected runtime methods. Understanding why Prebid.org excised these four modules clarifies how modern video and privacy architectures should be structured.
First, the removal of the paapi module follows Chromium's architectural pivot away from standalone browser-managed auction siloing under the Protected Audience API (formerly FLEDGE). Running two-tier on-device worklet auctions inside the browser created severe CPU contention and excessive memory allocation on lower-end devices, particularly when multiple SSPs attempted to register interest groups and execute runAdAuction() scripts simultaneously. Rather than maintaining complex client-side PAAPI orchestration inside the Prebid core, modern programmatic stacks rely on clean OpenRTB 2.6 contextual and first-party data signals coupled with Prebid Server auction mechanics.
Second, the removal of adpod, gamAdpod, and dfpAdServerVideo eliminates legacy client-side video podding and outdated DoubleClick for Publishers (DFP) vast-url assembly routines. In older Prebid versions, attempting to construct multi-slot video pods directly in the browser required heavy client-side caching, manual VAST XML wrapper unwrapping, and complex key-value concatenation (hb_pb_cat_dur) to enforce competitive exclusion in GAM. That architecture regularly caused race conditions with the Google IMA SDK and bloated client execution times.
In a modern Prebid 11.x production environment, long-form video and CTV podding are offloaded to Prebid Server using native OpenRTB 2.6 podding fields (imp.video.podid, podaduration, maxseq, and poddur) alongside VAST 4.3 Server-Side Ad Insertion (SSAI) or streamlined instream video orchestration. Publishers operating cross-screen video inventory should migrate legacy dfpAdServerVideo calls to the standard dfpVideo support module or route podded demand through a unified hybrid mediation layer that handles pod deduplication, IAB category exclusion, and mezzanine transcoding verification at the server edge rather than on the user's device.
Stricter mediaType Validation (rejectInvalidMediaTypes) and customGptSlotMatching
Prior to Prebid.js 11.0, the core auction manager exhibited a critical validation gap on single-format and multiformat ad units. If a publisher defined an adUnit containing only mediaTypes: { banner: { sizes: [[300, 250], [300, 600]] } }, a misconfigured or overly aggressive demand adapter could return a bid response tagged with mediaType: 'video' (such as an outstream unit) or mediaType: 'native'. Unless the publisher explicitly configured validation checks, Prebid could accept that mismatched bid into the auction, allow it to win based on a higher nominal CPM, and pass its targeting keys to GAM. When the winning creative executed inside a standard display safeFrame lacking an outstream renderer or native template, the slot rendered a blank frame, triggering discrepancy spikes between Prebid winning impression logs and GAM billable impressions.
Prebid.js 11.0 closes this loophole by setting rejectInvalidMediaTypes: true as the default auction behavior. During bid response ingestion in auction.js, the wrapper inspects the mediaType property of every incoming bid object against the keys present in the corresponding adUnit.mediaTypes specification. If a bidder returns a video payload for a banner-only ad unit, the bid is immediately discarded and logged to the Prebid error queue before floor validation or targeting key generation occurs. Publishers who intentionally rely on legacy adapter behavior (while refactoring their ad unit schemas) can temporarily override this via pbjs.setConfig({ rejectInvalidMediaTypes: false }), though doing so is strongly discouraged in production.
Alongside strict media type checking, Prebid.js 11.0 introduces customGptSlotMatching within the top-level configuration. Historically, when pbjs.setTargetingForGPTAsync() executed, Prebid matched its internal adUnit.code to active GPT slot objects by comparing the code against slot.getAdUnitPath() or slot.getSlotElementId(). While sufficient for static HTML pages, this default matching frequently broke in modern Single Page Applications (SPAs) built on React, Next.js, or Vue. In SPA architectures, virtual DOM hydration often appends dynamic hashes or route-specific UUIDs to container div IDs (for example, div-gpt-ad-inarticle-a8f92b), causing Prebid's default matcher to miss the target GPT slot and leave AdX to compete against an empty header bidding book.
The customGptSlotMatching function allows engineering teams to define a deterministic predicate function that receives a GPT Slot object and returns a custom matching function evaluated against each Prebid adUnit.code, utilizing slot-level targeting parameters or custom data attributes instead of brittle DOM IDs.
window.pbjs = window.pbjs || { que: [] };
window.googletag = window.googletag || { cmd: [] };
pbjs.que.push(function () {
pbjs.setConfig({
// Enforced by default in Prebid 11.0; declared explicitly for audit clarity
rejectInvalidMediaTypes: true,
enableTIDs: true,
// Deterministic GPT slot binding for SPA architectures with dynamic DOM IDs
customGptSlotMatching: function (gptSlot) {
const slotPlacementKey = gptSlot.getTargeting('hb_placement_id')[0];
const slotAdUnitPath = gptSlot.getAdUnitPath();
return function (adUnitCode) {
if (slotPlacementKey && adUnitCode === slotPlacementKey) {
return true;
}
return gptSlot.getSlotElementId() === adUnitCode || slotAdUnitPath === adUnitCode;
};
}
});
});
Dynamic vs. Package Floors with Price Floors Schema 2 (modelGroups & skipRate)
As buy-side DSPs deploy sophisticated bid shading algorithms that continuously probe the minimum clearing threshold of every impression, static ad server floor rules in GAM no longer capture full publisher yield. If a floor is applied only inside GAM Unified Pricing Rules (UPR), client-side and server-side Prebid adapters bid without upfront floor visibility or clear the wrapper auction at artificially shaded rates before losing to AdX or house line items in the ad server.
The Prebid Price Floors module addresses this by enforcing unified floor logic across both client-side adapters and Prebid Server connections prior to GAM key-value injection. Specifically, Price Floors Schema 2 (floorsSchemaVersion: 2) transforms the module from a flat lookup table into an experimentation engine capable of running weighted multi-model A/B tests directly inside the auction lifecycle via the modelGroups array.
In Schema 2, instead of defining a single values map at the root of the data object, publishers define an array of modelGroups. Each group contains a modelWeight (an integer or float representing relative traffic allocation), a modelName (passed into OpenRTB imp.ext.prebid.floors.floorRuleValue and analytics adapters for downstream attribution), a schema definition, and a values map. When a new auction initializes, the Price Floors module performs a weighted random selection across all defined modelGroups to assign a single active model for that auction. Furthermore, skipRate (an integer from 0 to 100) can be declared either globally at the root floors object or locally inside an individual model group. When triggered by the random number generator, skipRate bypasses floor enforcement for that auction while logging skipped: true to the analytics pipeline, creating a clean, uncontaminated holdout cohort to measure true RPM lift and fill rate elasticity.
Architecturally, publishers must balance dynamic endpoint fetching against inline package floors:
- Dynamic Floors via Endpoint: The wrapper requests an updated JSON floor payload from an edge CDN or real-time pricing service (
endpoint.url). To ensure the dynamic rules apply to the initial pageview auction, engineers configureauctionDelay(typically 100 to 150 milliseconds). If the endpoint responds within theauctionDelaybudget, the dynamic Schema 2 payload overrides the local configuration. - Inline Package Floors: If the dynamic endpoint encounters network latency exceeding
auctionDelayor returns an HTTP 5xx status, delaying the auction further degrades viewability. By declaring an inlinedataobject alongside theendpointconfiguration, Prebid immediately falls back to the bundled package floors onceauctionDelayexpires, preventing zero-floor exposure on cold-start auctions.
When writing rule sets inside a model group, engineers must understand Prebid's strict left-to-right rule specificity matching algorithm. Suppose a model's schema.fields array is ordered as ["mediaType", "size", "gptSlot"] with delimiter "|". When an ad request for a banner of size 300x250 on slot /12345/finance_top calls getFloor(), the engine evaluates candidate patterns by prioritizing exact matches on the leftmost column first. A rule key of "banner|*|*" (which matches column 1 exactly and uses wildcards for columns 2 and 3) will always beat "*|300x250|/12345/finance_top" (which uses a wildcard on column 1 despite having exact matches on columns 2 and 3). Therefore, schema.fields must always be ordered from lowest cardinality (most general, such as mediaType) on the left to highest cardinality (most specific, such as gptSlot or domain) on the right, or vice versa only when every high-priority rule explicitly populates the leftmost dimension.
pbjs.setConfig({
floors: {
enforcement: {
enforceJS: true,
enforcePBS: true,
floorDeals: false,
bidAdjustment: true
},
auctionDelay: 120,
floorMin: 0.15,
noFloorSignalBidders: ['selectiveDspAdapterA', 'selectiveSspAdapterB'],
endpoint: {
url: 'https://floors.skyboosts.com/v2/rules/pub-9482.json',
method: 'GET'
},
data: {
currency: 'USD',
floorsSchemaVersion: 2,
skipRate: 5,
modelGroups: [
{
modelName: 'aggressive_q4_high_viewability',
modelWeight: 70,
skipRate: 0,
schema: {
fields: ['mediaType', 'size', 'gptSlot'],
delimiter: '|'
},
values: {
'video|640x480|/102938/ctv_preroll': 8.50,
'banner|300x250|/102938/article_inread': 1.85,
'banner|728x90|/102938/header_leaderboard': 1.40,
'banner|300x250|*': 0.95,
'banner|*|*': 0.55,
'*|*|*': 0.30
},
default: 0.25
},
{
modelName: 'baseline_conservative_fill',
modelWeight: 30,
skipRate: 10,
schema: {
fields: ['mediaType', 'size', 'gptSlot'],
delimiter: '|'
},
values: {
'video|640x480|/102938/ctv_preroll': 6.75,
'banner|300x250|/102938/article_inread': 1.25,
'banner|728x90|/102938/header_leaderboard': 0.95,
'banner|300x250|*': 0.65,
'banner|*|*': 0.35,
'*|*|*': 0.20
},
default: 0.15
}
]
}
}
});
Prebid.js vs. Prebid Server Floors: noFloorSignalBidders and floorMin
In hybrid header bidding stacks where Prebid.js orchestrates both client-side adapters and server-to-server (S2S) demand via Prebid Server (s2sConfig), floor signaling requires granular calibration per demand partner. Not all buy-side bidding algorithms respond identically to an explicit imp.bidfloor parameter in the OpenRTB 2.6 bid request. While many tier-1 DSPs use the declared floor to unlock higher-tier campaign budgets that would otherwise be filtered out by QPS throttling, certain buyers use the incoming bidfloor as a hard bid-shading anchor, shading their second-price or first-price valuation down to just one cent above the signaled floor.
To prevent aggressive bid shading without sacrificing floor protection, Prebid.js (8.31+ and carried forward into 11.x) and Prebid Server Java (3.4+) support the noFloorSignalBidders configuration array. When a bidder code is listed in noFloorSignalBidders, the Price Floors module intercepts the adapter's bidRequest.getFloor() call during outbound request construction and returns 0 (suppressing the floor signal in the outgoing network payload). Similarly, when s2sConfig forwards the OpenRTB request to Prebid Server, ext.prebid.floors.noFloorSignalBidders instructs the server not to transmit imp.bidfloor to those specific S2S demand partners. Crucially, the underlying calculated floor remains active during the auction clearing phase (enforceJS: true and enforcePBS: true). If a bidder in noFloorSignalBidders submits a shaded bid below the active model group's threshold, the bid is marked as a floor violation and rejected from winning the Prebid auction.
Additionally, engineering teams must configure floorMin to establish an absolute publisher guardrail whenever dynamic floors or bidder-level bidCpmAdjustment functions are active. When floorMin is defined at the top-level floors object (for example, floorMin: 0.15), even if an external dynamic pricing model calculates a floor of 0.05 for low-tier remnant inventory, Prebid overrides the model output and enforces the 0.15 minimum. Publishers utilizing Sky Boosts can track how noFloorSignalBidders, floorMin, and Schema 2 modelGroups impact bid density, win rate, and net eCPM across our granular reporting dashboards in real time.
Privacy Governance: Activity Controls and Strict storageControl Enforcement
Beyond auction mechanics and yield optimization, Prebid.js 11.x fundamentally overhauls how client-side privacy rules are evaluated and enforced. In earlier versions of Prebid, privacy governance was fragmented across disparate configuration blocks: TCF enforcement relied on consentManagement.gdpr.rules, US privacy relied on consentManagement.usp, and user ID restrictions lived inside userSync. That fragmentation made it difficult for publisher data protection officers to audit exactly when a specific bidder or RTD module accessed device storage or transmitted user identifiers.
Prebid.js 11.x consolidates all privacy enforcement onto Prebid.js Activity Controls paired with strict storageControl execution. In Prebid.js 11.0, the storageControl module defaults to strict enforcement (enforcement: 'strict'). Under this default regime, whenever any module invokes Prebid's internal storageManager API to read or write a cookie or localStorage entry, the wrapper verifies whether the calling component has disclosed its storage usage and holds a valid GVL ID (under GDPR TCF scope) or explicit publisher permission. If an adapter or submodule attempts undisclosed storage access, storageControl blocks the read/write operation deterministically.
Underneath storageControl, the Activity Controls engine evaluates every restricted operation against a prioritized registry of allowActivities rules. Understanding the priority hierarchy is vital for debugging why a specific module is permitted or blocked:
- Priority 1 (Publisher Configuration): Any rule declared directly by the publisher via
pbjs.setConfig({ allowActivities: { ... } })is assigned a default priority of1, giving publisher-authored rules top precedence. - Priority 10 (Compliance Modules): Rules registered dynamically by Prebid privacy enforcement modules (such as
consentManagementTcf,consentManagementGpp, orgppControl_usnat) are assigned a default priority of10. - Tie-Breaking Logic: The engine evaluates rules in ascending numerical order (priority
1before priority10). If multiple rules at the exact same priority level match the component and condition, a rule returningallow: falsealways overrides a rule returningallow: true. Once a priority tier produces a definitive result, lower-priority tiers (higher numbers) are not evaluated.
Controlling accessDevice, accessRequestCredentials, transmitUfpd, and transmitEids
To implement zero-leakage privacy governance in Prebid.js 11.x, AdOps architects must configure rules across the complete set of ten core Activity Controls:
accessDevice: Governs whether a component (bidder,userId,rtd, oranalytics) is permitted to read from or write to browser storage (cookieorhtml5localStorage). This is the primary activity evaluated bystorageControland TCF Purpose 1 checks.accessRequestCredentials: Introduced in Prebid 10.x and refined in Prebid 11.15, this activity governs whether outgoing HTTP requests (fetchorXMLHttpRequest) initiated by bidder adapters or user sync calls are allowed to attach browser credentials (settingwithCredentials = trueorcredentials: 'include'). Even ifaccessDeviceblocks JavaScriptdocument.cookieaccess on the publisher's first-party domain, a browser will still attach third-party cookies to cross-origin ad requests unlessaccessRequestCredentialsis denied for unconsented vendors.enrichEidsandtransmitEids: Prebid separates the generation of Extended Identifiers (enrichEids, executed by User ID submodules like UID2, ID5, or SharedID) from the outbound distribution of those IDs (transmitEids, which populatesuser.ext.eidsin the OpenRTB bid stream). A publisher can allowenrichEidsglobally so IDs are cached for GAM Secure Signals or curated SSP partners, while restrictingtransmitEidsto an approved allowlist of demand adapters.enrichUfpdandtransmitUfpd: Controls the enrichment and outbound transmission of User First-Party Data (ortb2.user.dataandortb2.user.ext.data). DenyingtransmitUfpdfor unverified third-party bidders prevents proprietary publisher audience segments from leaking into external DSP identity graphs while preserving those signals for direct and semi-direct demand.transmitPreciseGeo: Governs whether high-precision coordinates (device.geo.latanddevice.geo.lon) are transmitted intact or obfuscated/stripped from the bid request.fetchBidsandsyncUser: Controls whether a bidder adapter is allowed to participate in an auction (fetchBids) or trigger cookie-sync pixels/iframes (syncUser) post-auction.reportAnalytics: Governs whether analytics adapters can dispatch auction telemetry. Beginning in Prebid.js 11.18,reportAnalyticspermissions are dynamically re-checked whenever the user's consent state mutates mid-session (for example, when a user revokes analytics consent via a resurfaced CMP modal), ensuring telemetry pipelines immediately halt data dispatch upon revocation.
pbjs.setConfig({
// Prebid 11.0 defaults to 'strict'; explicitly declared for compliance verification
storageControl: {
enforcement: 'strict'
},
consentManagement: {
gdpr: {
cmpApi: 'iab',
timeout: 5000,
defaultGdprScope: true
},
gpp: {
cmpApi: 'iab',
timeout: 5000
}
},
// Priority 1 publisher-level Activity Controls
allowActivities: {
// Block cross-origin credentialed requests for non-approved adapters
accessRequestCredentials: {
default: false,
rules: [
{
condition: function (params) {
const trustedPartners = ['skyboosts', 'curatedSspPartnerA'];
return params.componentType === 'bidder' &&
trustedPartners.includes(params.componentName);
},
allow: true,
priority: 1
}
]
},
// Permit EID enrichment locally, but gate outbound OpenRTB transmission
enrichEids: {
default: true
},
transmitEids: {
default: false,
rules: [
{
condition: function (params) {
const approvedEidReceivers = ['skyboosts', 'pbsAdapter', 'curatedSspPartnerA'];
return params.componentType === 'bidder' &&
approvedEidReceivers.includes(params.componentName);
},
allow: true,
priority: 1
}
]
},
// Protect first-party publisher audience segments (ortb2.user.data) from data leakage
transmitUfpd: {
default: false,
rules: [
{
condition: function (params) {
return params.componentType === 'bidder' &&
params.componentName === 'skyboosts';
},
allow: true,
priority: 1
}
]
},
// Strip precise lat/long coordinates across all client-side requests
transmitPreciseGeo: {
default: false
}
}
});
Before promoting a Prebid.js 11.x bundle to 100% of production traffic, engineering and AdOps teams should execute a four-step verification protocol on a 5% canary traffic slice:
- Inspect
pbjs.getNoBids()and console warnings to verify zero impressions are dropped due torejectInvalidMediaTypesmismatches on multiformat slots. - Validate that SPA route transitions bind 100% of Prebid auction winners to GAM slots via
customGptSlotMatching. - Audit
pbjs.getBidResponses()to confirmfloorsSchemaVersion: 2model group assignments (floorRuleValue) andnoFloorSignalBiddersbehavior match expected holdout and test weights. - Verify in the browser Network tab that unapproved bidders execute with
credentials: 'omit'underaccessRequestCredentialsand that zerostorageControlstrict-mode violations appear for active User ID and RTD submodules.