The normal way to add a Prebid.js bidder adapter is registerBidder(spec) from the bidder factory. It is the right answer for most adapters:
- you create a module under
modules; - you implement
isBidRequestValid,buildRequests, andinterpretResponse; - you build Prebid.js with that module included;
- you submit code and docs if the adapter should become public.
The official guide covers that path well, so this article is not another registerBidder tutorial.
This article is about the other path: registering a bidder at runtime with pbjs.registerBidAdapter.
That path is useful when you need to inject an adapter into an existing Prebid.js bundle without rebuilding it. This is not the common case, but it comes up in real systems: experiments, publisher-side integrations, wrappers, private demand plumbing, debugging adapters, and situations where the page already ships a fixed Prebid build.
The API surface is tiny. The important part is the reverse-engineered piece around it: what a runtime adapter has to return so Prebid treats its response as a real bid and matches it back to the original ad unit.
The Two Paths
Use the official registerBidder path when you control the Prebid.js build:
import { registerBidder } from 'src/adapters/bidderFactory';
const spec = {
code: 'example',
isBidRequestValid(bid) {
return Boolean(bid.params.placementId);
},
buildRequests(validBidRequests, bidderRequest) {
return {
method: 'POST',
url: 'https://example.com/bid',
data: JSON.stringify({ bids: validBidRequests, bidderRequest }),
};
},
interpretResponse(serverResponse) {
return serverResponse.body.bids;
},
};
registerBidder(spec);
That is stable, documented, and reviewable. It is also build-time oriented.
Use registerBidAdapter when you need runtime registration:
window.pbjs.que.push(() => {
window.pbjs.registerBidAdapter(function runtimeAdapter() {
return {
callBids(bidderRequest, addBidResponse, done) {
// Fetch or synthesize bids here.
done();
},
};
}, 'runtime-bidder');
});
After this call, runtime-bidder can appear in ad unit bidder lists. The hard part is not registration. The hard part is creating bid objects that Prebid accepts downstream.
Why This Is Not Just the Official Adapter Spec
registerBidder gives you a clean adapter contract. Prebid owns the adapter lifecycle around that contract: validation, request building, response interpretation, and bid normalization.
registerBidAdapter skips most of that comfortable shape. You are closer to the internal adapter manager. Your callBids function gets request data and callbacks. You are responsible for returning bid objects that already contain the fields Prebid needs to correlate the response with the auction.
That is why this technique is powerful and brittle at the same time.
It lets you do things the public adapter spec does not model neatly, especially when the adapter has to be injected after the bundle is already on the page. It also means Prebid internals can break you between major versions.
Prebid 8 and 9: Use createBid
In the Prebid 8 and 9 builds I tested, the easiest path was pbjs.createBid(status, bidRequest). The status value 1 is the “good bid” status used internally.
const BIDDER_CODE = 'runtime-bidder';
const GOOD = 1;
window.pbjs.que.push(() => {
window.pbjs.registerBidAdapter(function runtimeAdapter() {
return {
callBids(bidderRequest, addBidResponse, done) {
bidderRequest.bids.forEach((bidRequest) => {
const bid = window.pbjs.createBid(GOOD, bidRequest);
// These fields let Prebid match the response to the original request.
bid.requestId = bidRequest.bidId;
bid.adUnitId = bidRequest.adUnitId;
bid.auctionId = bidRequest.auctionId;
bid.adUnitCode = bidRequest.adUnitCode;
bid.bidderCode = bidRequest.bidder;
// The bid payload depends on what you are returning.
bid.cpm = 10;
bid.currency = 'USD';
bid.width = 1;
bid.height = 1;
bid.ttl = 60;
bid.netRevenue = false;
bid.creativeId = 'runtime-test';
bid.ad = '<div>Runtime bid</div>';
addBidResponse(bidRequest.adUnitCode, bid);
});
done();
},
};
}, BIDDER_CODE);
});
This is the shape I originally found by reading Prebid source and following how bid responses flow through the auction.
Prebid 10: Build the Bid Object Yourself
Prebid 10 changed enough internals that I do not rely on createBid there. The runtime adapter route still works, but the safer approach is to build the bid object manually.
function createRuntimeBid(bidRequest) {
return {
// Correlation fields.
bidId: bidRequest.bidId,
requestId: bidRequest.bidId,
adId: bidRequest.bidId,
bidder: bidRequest.bidder,
bidderCode: bidRequest.bidder,
adUnitId: bidRequest.adUnitId,
adUnitCode: bidRequest.adUnitCode,
auctionId: bidRequest.auctionId,
// Shape copied from the original request where useful.
mediaTypes: bidRequest.mediaTypes || {},
sizes: bidRequest.sizes || [],
// Actual bid payload.
cpm: 10,
currency: 'USD',
width: 1,
height: 1,
ttl: 600,
netRevenue: false,
creativeId: 'runtime-test',
timeToRespond: 0,
ad: '<div>Runtime bid</div>',
getSize() {
return `${this.width}x${this.height}`;
},
};
}
window.pbjs.que.push(() => {
window.pbjs.registerBidAdapter(function runtimeAdapter() {
return {
callBids(bidderRequest, addBidResponse, done) {
bidderRequest.bids.forEach((bidRequest) => {
addBidResponse(bidRequest.adUnitCode, createRuntimeBid(bidRequest));
});
done();
},
};
}, 'runtime-bidder');
});
The correlation fields are the important part. If requestId, adUnitCode, auctionId, or bidder identity fields do not line up with the request Prebid has in memory, the bid may exist as an object but fail to behave like a normal auction response.
Version Notes
I have used this pattern with Prebid 8.51.0 and verified the runtime adapter approach in Prebid 9 and 10 era builds. Treat that as a field note, not a compatibility promise from Prebid.
Before upgrading Prebid, run a smoke test:
- Register the runtime adapter inside
pbjs.que. - Add an ad unit that references the runtime bidder code.
- Run a small auction with
pbjs.requestBids. - Confirm that
addBidResponseis called. - Confirm that
pbjs.getBidResponses()contains the bid under the expected ad unit. - Confirm that the bid reaches targeting/rendering in your real integration path.
If any of those steps fail, inspect the bid object that a normal adapter returns in the same Prebid version and compare its fields with your runtime bid.
Why Bother With This
This is not a replacement for a real public adapter. If you can use registerBidder, use it.
Runtime injection is valuable when the build boundary is the problem:
- a publisher already has a locked Prebid.js bundle;
- a wrapper needs to add a private bidder without recompiling the page bundle;
- you need to prototype demand behavior before investing in a full adapter;
- you need an adapter-like test double for diagnostics;
- you are integrating something that does not fit the normal request/response shape yet.
That is the reason this article exists. The useful knowledge is not that registerBidAdapter can be called. The useful knowledge is the minimal shape that makes Prebid accept the response as part of an auction.
Risks
This is a thinly documented API surface and a partly reverse-engineered bid shape. Keep that in mind.
The main risks:
- Prebid can change internal bid fields between major versions.
- The callback signature is not as richly documented as the official adapter spec.
- Privacy, consent, floors, currency, media type, native, and video behavior may require additional fields.
- A bid object that works for a simple banner may not be enough for more complex media.
- If you use this in production, you own the compatibility tests.
Keep the runtime adapter small. Keep it covered by a real browser smoke test. Re-test after every Prebid upgrade.
Practical Rule
Use registerBidder for productized adapters.
Use registerBidAdapter when you deliberately need runtime injection and accept that you are working closer to Prebid internals.
When you take the internal path, make the contract explicit in your own code: bidder code, request correlation fields, bid payload fields, and version smoke tests. That is what turns a fragile hack into a maintainable escape hatch.