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

ScopeSet withWho receives it
Every auctionpbjs.setConfig({ ortb2 })All bidders
One auctionpbjs.requestBids({ ortb2 })All bidders in that auction
Some bidderspbjs.setBidderConfig({ bidders, config: { ortb2 } })Only the listed bidders
One ad unitadUnit.ortb2ImpEvery bidder on the ad unit
One bidder on one ad unitadUnit.bids[].ortb2ImpThat 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:

ValueOpenRTB 2.6 pathWhere Prebid.js keeps it
GDPR appliesregs.gdprregs.ext.gdpr
TCF consent stringuser.consentuser.ext.consent
US Privacy stringregs.us_privacyregs.ext.us_privacy
Extended IDsuser.eidsuser.ext.eids
Supply chainsource.schainsource.ext.schain
GPP string and sectionsregs.gpp, regs.gpp_sidSame as 2.6
Structured user agentdevice.suaSame 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:

FieldSource
Request bodybidderRequest.ortb2, the global and bidder-specific data merged
idAn id already in ortb2, else a generated UUID
tmaxThe bidder's timeout for this auction
site, app, doohOnly one is kept, in the order dooh, app, site
Each impStarts as the bid's merged ortb2Imp
imp.idPrebid's bid ID, not the ad unit code
imp.bannerformat from mediaTypes.banner.sizes unless ortb2Imp sets it, plus pos and topframe
imp.video, imp.audioThe mediaTypes fields that share OpenRTB names; video w and h from the first playerSize
imp.nativeThe ad unit's native request as a JSON string, with ver
imp.secure1 unless already set
imp.bidfloor, imp.bidfloorcurThe Price Floors module, when installed
curThe 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, and device.ifa.
  • transmitEids denied removes user.eids and user.ext.eids.
  • transmitPreciseGeo denied rounds latitude and longitude to two decimals, zeroes the last octet of device.ip, and zeroes the last 64 bits of device.ipv6.
  • transmitTid withholds source.tid and imp.ext.tid unless the publisher sets enableTIDs. 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

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.