How Prebid.js builds an OpenRTB 2.6 request with ortb2 and ortb2Imp
Prebid.js keeps first-party data as fragments of an OpenRTB request: request-wide fields in ortb2 and impression fields in ortb2Imp. Bid adapters built on the shared ortbConverter library start each request from those two objects, so a field you set there arrives at the same path, unless a privacy control removes it or the adapter overwrites it.
Where first-party data goes
| Scope | Set with | Who receives it |
|---|---|---|
| Every auction | pbjs.setConfig({ ortb2 }) | All bidders |
| One auction | pbjs.requestBids({ ortb2 }) | All bidders in that auction |
| Some bidders | pbjs.setBidderConfig({ bidders, config: { ortb2 } }) | Only the listed bidders |
| One ad unit | adUnit.ortb2Imp | Every bidder on the ad unit |
| One bidder on one ad unit | adUnit.bids[].ortb2Imp | That bidder, merged over the ad unit's ortb2Imp |
pbjs.setConfig({
ortb2: {
site: {
cattax: 9,
cat: ["653"],
pagecat: ["655"],
content: { language: "en" }
},
bcat: ["IAB25-3", "IAB26"]
}
});
pbjs.setBidderConfig({
bidders: ["exampleBidder"],
config: { ortb2: { user: { data: [{ name: "pub-segments.example", segment: [{ id: "412" }] }] } } }
});
pbjs.addAdUnits({
code: "article-mid-1",
mediaTypes: { banner: { sizes: [[300, 250], [336, 280]] } },
ortb2Imp: {
ext: { data: { section: "gear" } }
},
bids: [{ bidder: "exampleBidder", params: { placementId: "88213" } }]
});Prebid's documentation says publisher values generally take precedence, with exceptions: the TCF consent module writes the consent fields, and the geolocation module writes device.geo. Setting a field to null turns off a value Prebid would fill in. User data belongs in the request-wide config, because Prebid does not support user data on an ad unit.
Bidder-specific ortb2 is deep-merged into the global object, and arrays are appended rather than replaced, with duplicates dropped. In the example, exampleBidder receives the global site and bcat plus its own user.data. A bidder-specific bcat would add to the global list, not narrow it. The global bcat here uses 1.0 codes and needs no cattax; a list in a newer taxonomy needs one, as the category blocking guide explains.
Fields Prebid.js fills in by itself
When the publisher has not set them, Prebid.js populates site.page, site.ref, site.domain, site.publisher.domain, site.keywords, device.w, device.h, device.dnt, device.language, device.ua, device.sua, device.ext.vpw, device.ext.vph, device.ext.webdriver, regs.coppa, and regs.ext.gpc. Everything else, including categories, content metadata, and block lists, comes from the publisher or a module.
Some 2.6 fields live in ext
Prebid.js accepts several privacy and identity fields at either their OpenRTB 2.5 or 2.6 path, then normalizes them to one location. In the Prebid.js 11 source checked in November 2026, that location is the 2.5 one for most of them:
| Value | OpenRTB 2.6 path | Where Prebid.js keeps it |
|---|---|---|
| GDPR applies | regs.gdpr | regs.ext.gdpr |
| TCF consent string | user.consent | user.ext.consent |
| US Privacy string | regs.us_privacy | regs.ext.us_privacy |
| Extended IDs | user.eids | user.ext.eids |
| Supply chain | source.schain | source.ext.schain |
| GPP string and sections | regs.gpp, regs.gpp_sid | Same as 2.6 |
| Structured user agent | device.sua | Same as 2.6 |
The converter copies ortb2 as it is, so a bidder receives this mix unless its adapter moves the fields. Validated as 2.6, such a request draws openrtb.field.moved for regs.ext.gdpr and user.ext.consent. For endpoints that speak only 2.5, Prebid.js ships an ortb2.5Translator library that moves the remaining 2.6 fields into ext and folds kwarray into keywords. The privacy reference lists the 2.6 locations.
How the ortbConverter builds the request
Adapters that talk to an OpenRTB endpoint can use the converter for most of buildRequests and interpretResponse. An adapter customizes the output through hooks and gives defaults for the bid response fields OpenRTB has no equivalent for:
import { ortbConverter } from '../libraries/ortbConverter/converter.js';
const converter = ortbConverter({
context: { netRevenue: true, ttl: 300 },
imp(buildImp, bidRequest, context) {
const imp = buildImp(bidRequest, context);
imp.tagid = bidRequest.params.placementId;
return imp;
}
});
// buildRequests
const data = converter.toORTB({ bidRequests, bidderRequest });
// interpretResponse
const { bids } = converter.fromORTB({ request: data, response: serverResponse.body });The default processors then fill the request:
| Field | Source |
|---|---|
| Request body | bidderRequest.ortb2, the global and bidder-specific data merged |
id | An id already in ortb2, else a generated UUID |
tmax | The bidder's timeout for this auction |
site, app, dooh | Only one is kept, in the order dooh, app, site |
| Each imp | Starts as the bid's merged ortb2Imp |
imp.id | Prebid's bid ID, not the ad unit code |
imp.banner | format from mediaTypes.banner.sizes unless ortb2Imp sets it, plus pos and topframe |
imp.video, imp.audio | The mediaTypes fields that share OpenRTB names; video w and h from the first playerSize |
imp.native | The ad unit's native request as a JSON string, with ver |
imp.secure | 1 unless already set |
imp.bidfloor, imp.bidfloorcur | The Price Floors module, when installed |
cur | The Currency module, when installed |
Video needs care. Only fields with OpenRTB names are copied from mediaTypes.video, and that list leaves out companionad, durfloors, and ext. Set those in ortb2Imp.video, which the converter merges over the mediaTypes values. When an ad unit has no imp.video.plcmt, Prebid.js sets 4 for outstream (or a legacy placement of 2, 3, or 4) and 2 when playbackmethod includes 2 or 6. The placement vs plcmt guide covers those values.
On the way back, the converter maps Bid.mtype to the Prebid media type and skips any bid that has neither mtype nor a media type set by the adapter. It copies Bid.adomain to meta.advertiserDomains, the first Bid.cat entry to meta.primaryCatId, and Bid.exp to the bid's TTL.
What can still change before the bidder sees it
Activity controls, set by the publisher or by privacy modules, act on the merged data for each bidder:
- transmitUfpd denied removes
user.data,user.ext.data,user.keywords,user.kwarray,user.id,user.buyeruid,user.yob,user.gender,user.customdata, anddevice.ifa. - transmitEids denied removes
user.eidsanduser.ext.eids. - transmitPreciseGeo denied rounds latitude and longitude to two decimals, zeroes the last octet of
device.ip, and zeroes the last 64 bits ofdevice.ipv6. - transmitTid withholds
source.tidandimp.ext.tidunless the publisher setsenableTIDs. They have been off by default since Prebid.js 8. The tid post covers both IDs.
The Prebid Server adapter in Prebid.js uses the same converter with the pbsExtensions processors. It puts each bidder's params in imp.ext.prebid.bidder, bidder-specific ortb2Imp in imp.ext.prebid.imp, and bidder-specific ortb2 in ext.prebid.bidderconfig, and sets ext.prebid.channel.name to pbjs. The Prebid Server extensions guide follows that request into the server.
The only proof of what a bidder received is the request on the wire. Copy the POST body for one bidder from the browser's network panel and paste it into the tester as OpenRTB 2.6.
Sources
- Prebid.js: First Party Data feature
- Prebid.js: How to add a new bidder adapter (ortb2, ortb2Imp, ortbConverter)
- Prebid.js: Ad unit reference (ortb2Imp and mediaTypes)
- Prebid.js ortbConverter README and default processors
- Prebid.js FPD normalization (src/fpd/normalize.js) and activity redaction rules (src/activities/redactor.ts)
Questions people ask
How do ortb2 and ortb2Imp differ?
ortb2 holds request-wide OpenRTB fields such as site, app, device, user, regs, source, and block lists. ortb2Imp holds fields for one impression and is set on an ad unit, or on one bidder inside an ad unit. Adapters built on ortbConverter start the request from ortb2 and each imp from ortb2Imp.
Does every Prebid.js adapter forward ortb2 data?
No. Adapters built on the shared ortbConverter merge ortb2 and ortb2Imp into the request by default, but adapters that build requests by hand pick fields themselves. Prebid's first-party data documentation warns that not every adapter reads the standard locations, so capture the outgoing request to be sure.
Why does a Prebid.js request carry gdpr in regs.ext?
Prebid.js normalizes first-party data so the GDPR flag, the TCF consent string, the US Privacy string, extended IDs, and the supply chain sit under ext, the OpenRTB 2.5 locations, while GPP and device.sua keep their 2.6 paths. An adapter whose endpoint expects 2.6 has to move those fields.