• Runtime Bidder Injection in Prebid.js

    A small blue-gray gear meshes with a larger gear, surrounded by gears with orange-lit hubs.

    This adapter started as part of a solution for intercepting price floors. That is why the example returns a 1×1 bid. The floor-interception logic is outside this post; the piece worth isolating is how to register a bidder in an already-loaded Prebid.js bundle and return a response the auction can use.

    The example is deliberately small: one synchronous banner response, no bidding endpoint, no user syncs. Its price and creative are synthetic. It demonstrates a working runtime adapter, not the complete floors integration or a production demand source.

    Register without rebuilding

    For a conventional adapter, use registerBidder(spec) and the bidder factory. That route handles request validation, response interpretation, and normalization around your adapter.

    When the bundle is already on the page, pbjs.registerBidAdapter lets you register an adapter at runtime. You supply a constructor returning an object with callBids. Prebid calls it with the bidder request, an addBidResponse callback, and a done callback.

    Registration is the easy part. The factory normally fills in properties that the rest of Prebid expects. On this lower-level path, you supply that bid object yourself.

    A minimal working response

    The example below was checked with Prebid.js 10.0.0, using its all-modules development bundle in an isolated browser test. The bid appeared in getBidResponses(), produced 1x1 targeting, and rendered into a test iframe. Use that bundle for reproduction, not as a production build recommendation.

    js
    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 || [],
    
        // Explicit format, source, and metadata for this banner response.
        mediaType: 'banner',
        source: 'client',
        meta: {},
    
        // Synthetic payload for this example.
        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');
    });

    1×1 is intentional: it comes from the floors use case. The fixed CPM of 10, the USD currency, and the small HTML payload make this example self-contained; they are not pricing advice. Run it on a test page, away from live demand.

    Here, “minimal” describes the example’s scope. It retains the request context used by the original integration rather than claiming that every copied property is indispensable in every Prebid build.

    What the fields do

    Fields Role in this example
    requestId Links the response to bidRequest.bidId. Copy it from the request being answered.
    adUnitCode, adUnitId, auctionId Carry the slot and auction context. Pass the same ad unit code to addBidResponse.
    bidderCode, bidder Identify the runtime bidder; bidderCode is also used for targeting.
    adId Identifies the response for targeting and rendering. Reusing the request’s bid ID works here because the adapter returns one response per request.
    mediaType, source, meta Declare a client-side banner and provide the metadata object expected by response hooks.
    cpm, currency, ttl, netRevenue, creativeId, ad Describe the synthetic bid: price, currency, lifetime in seconds, revenue convention, creative identity, and markup.
    width, height, getSize() Describe its 1×1 dimensions. The targeting code calls getSize().

    The remaining bidId, mediaTypes, and sizes preserve request context. timeToRespond starts at zero here; the auction computes it from timestamps.

    The source makes these boundaries easier to follow: Prebid 10.0.0’s bid factory creates the base object, the bidder factory adds response metadata, and the auction code prepares the bid and targeting.

    meta: {} is a small but useful example of why the build matters. In a browser test, removing it caused this example to fail in the all-modules bundle: the dchain module tried to write bid.meta.dchain. Supplying the object let the response through. Setting mediaType: 'banner' also gives targeting an explicit format instead of leaving hb_format empty.

    Check the auction

    Load Prebid, run the registration snippet, then run this on the same test page:

    js
    window.pbjs.que.push(() => {
      window.pbjs.addAdUnits({
        code: 'runtime-test',
        mediaTypes: { banner: { sizes: [[1, 1]] } },
        bids: [{ bidder: 'runtime-bidder', params: {} }],
      });
    
      window.pbjs.requestBids({
        adUnitCodes: ['runtime-test'],
        bidsBackHandler() {
          console.log(window.pbjs.getBidResponses()['runtime-test']);
          console.log(
            window.pbjs.getAdserverTargetingForAdUnitCode('runtime-test'),
          );
        },
      });
    });

    In Prebid 10.0.0, expect one bid in the response array and targeting values including hb_bidder: 'runtime-bidder', hb_size: '1x1', and hb_format: 'banner'. The hb_adid value should match the returned bid’s adId. In a separate test iframe, pbjs.renderAd(iframe.contentDocument, bid.adId) can check the markup path too; rendering completes asynchronously.

    If callBids never runs, check registration order and the bidder code on the ad unit. If it runs but no bid appears, enable Prebid debug logging and inspect callback errors, rejection events, and request IDs. Calling addBidResponse alone does not prove acceptance. If the bid appears but targeting or rendering fails, inspect the bid shape and the modules in that exact build.

    Call done() once after submitting this request’s responses. This example is synchronous; an adapter that fetches bids must also finish its no-bid and error paths.

    Earlier versions and integration limits

    The original integration used Prebid 8.51.0 and was also used with Prebid 9 and 10 era builds. In the older 8/9 code, pbjs.createBid(1, bidRequest) supplied the base bid before the payload was assigned. Prebid 10 removed the public createBid API, as documented in its release notes. The internal factory still exists in the source; it is no longer exposed as pbjs.createBid.

    The browser check above covers a synthetic banner on one pinned version. It does not validate the floor-interception solution, consent handling, currency conversion, or native/video bids. Those depend on the surrounding integration and its modules. Re-run the check against your actual bundle when upgrading: the runtime registration method is documented, but the bid object still has to meet internal contracts.