Prebid.js is the most widely adopted header bidding solution in programmatic advertising. It connects your ad inventory to dozens of demand sources simultaneously, forcing them to compete in a real-time auction before Google Ad Manager makes its final ad decision. The result is typically a 20-40% revenue increase over running GAM waterfall alone.

But setting up Prebid.js correctly requires precise configuration across both your website code and your GAM account. A misconfigured price granularity setting can leave money on the table. Missing key-value pairs in GAM mean bids never get matched to line items. A timeout that is too aggressive cuts off bidders before they can respond.

This guide walks through every step of the integration, from downloading Prebid.js to verifying that bids flow correctly into GAM. By the end, you will have a working Prebid.js + GAM setup that you can optimize and scale.

Prerequisites

Before you begin, make sure you have the following in place:

Step 1: Add Prebid.js to Your Page

Prebid.js is a modular library. You download a custom build that includes only the bidder adapters and modules you need, which keeps the file size manageable.

Download a Custom Build

Go to prebid.org/download.html and select the bidder adapters you need (for example, appnexusBidAdapter, rubiconBidAdapter, ixBidAdapter). Also include these essential modules:

Click "Get Prebid.js" to download the built file. A typical build with 5-8 bidder adapters is 80-150 KB gzipped.

Load Prebid.js on Your Page

Add Prebid.js to your page head, before the GPT library. The load order matters: Prebid needs to run its auction and set targeting before GPT requests ads from GAM.

<!-- Prebid.js - load first -->
<script async src="/js/prebid.js"></script>

<!-- GPT - load after Prebid -->
<script async src="https://securepubads.g.doubleclick.net/tag/js/gpt.js"></script>

Both scripts use the async attribute so they do not block page rendering. Prebid.js will queue its commands and execute them once the library loads.

Initialize the Command Queues

Immediately after the script tags, initialize the command queues for both Prebid and GPT:

<script>
  var pbjs = pbjs || {};
  pbjs.que = pbjs.que || [];

  var googletag = googletag || {};
  googletag.cmd = googletag.cmd || [];
</script>

This ensures that any commands you push to these queues will execute in order once the respective libraries finish loading.

Step 2: Configure Bidder Adapters

Each bidder adapter needs placement-specific parameters that map your ad slots to the bidder inventory. These parameters come from your account setup with each SSP.

Define Ad Units with Bidders

Here is a complete example configuration with three bidders (AppNexus, Rubicon, Index Exchange) across two ad slots:

var adUnits = [
  {
    code: 'div-leaderboard',           // matches your GPT div ID
    mediaTypes: {
      banner: {
        sizes: [[728, 90], [970, 250]] // accepted creative sizes
      }
    },
    bids: [
      {
        bidder: 'appnexus',
        params: {
          placementId: 12345678        // from your AppNexus account
        }
      },
      {
        bidder: 'rubicon',
        params: {
          accountId: 11111,            // from Rubicon/Magnite
          siteId: 22222,
          zoneId: 33333
        }
      },
      {
        bidder: 'ix',
        params: {
          siteId: '44444',             // from Index Exchange
          size: [728, 90]
        }
      }
    ]
  },
  {
    code: 'div-sidebar',
    mediaTypes: {
      banner: {
        sizes: [[300, 250], [300, 600]]
      }
    },
    bids: [
      {
        bidder: 'appnexus',
        params: {
          placementId: 87654321
        }
      },
      {
        bidder: 'rubicon',
        params: {
          accountId: 11111,
          siteId: 22222,
          zoneId: 55555
        }
      },
      {
        bidder: 'ix',
        params: {
          siteId: '66666',
          size: [300, 250]
        }
      }
    ]
  }
];

Key Points About Bidder Configuration

Step 3: Set Up Price Granularity

Price granularity controls how Prebid.js rounds bid prices before passing them to GAM as key-value pairs. This setting directly impacts how many line items you need in GAM and how accurately bids compete with your other demand.

Granularity Options

Prebid.js offers five built-in granularity settings:

Low granularity: Rounds bids to $0.50 increments up to $5.00. Creates buckets like $0.50, $1.00, $1.50, through $5.00. Requires approximately 10 line items. Best for: testing environments or very low-traffic sites where managing many line items is not feasible.

Medium granularity (default): Rounds to $0.10 increments up to $20.00. Buckets like $0.10, $0.20, $0.30, through $20.00. Requires approximately 200 line items. Best for: most publishers. Provides good revenue accuracy without overwhelming line item management.

High granularity: Rounds to $0.01 increments up to $20.00. Buckets from $0.01 through $20.00 in penny increments. Requires approximately 2,000 line items. Best for: high-traffic publishers where the additional revenue precision justifies the management overhead.

Auto granularity: Uses variable increments. $0.05 increments up to $5, $0.10 up to $10, $0.50 up to $20. Requires approximately 170 line items. Best for: publishers who want more precision at lower price points (where most bids land) without the full overhead of high granularity.

Dense granularity: $0.01 increments up to $3, then $0.05 up to $8, then $0.50 up to $20. Requires approximately 400 line items. Best for: publishers with most bids in the $0.50-$3.00 range who want penny-level precision where it matters most.

Configure Price Granularity

pbjs.que.push(function() {
  pbjs.setConfig({
    priceGranularity: 'medium',  // or 'low', 'high', 'auto', 'dense'
    currency: {
      adServerCurrency: 'USD'
    },
    enableSendAllBids: false     // send only the winning bid per ad unit
  });
});

Custom Granularity

For maximum control, define custom price buckets:

pbjs.setConfig({
  priceGranularity: {
    buckets: [
      { precision: 2, min: 0, max: 5, increment: 0.05 },
      { precision: 2, min: 5, max: 10, increment: 0.10 },
      { precision: 2, min: 10, max: 20, increment: 0.50 },
      { precision: 2, min: 20, max: 50, increment: 1.00 }
    ]
  }
});

This custom configuration uses tight $0.05 increments for the $0-$5 range (where most programmatic bids fall), then widens to $0.10, $0.50, and $1.00 increments at higher price points. This reduces line item count while maintaining precision where it matters most for revenue.

Revenue Impact of Granularity

Switching from low ($0.50 increments) to medium ($0.10 increments) granularity typically recovers 3-8% of revenue that was being lost to rounding. A bid of $1.47 rounds to $1.00 with low granularity (losing $0.47) but to $1.40 with medium (losing only $0.07). Over millions of impressions, that rounding loss adds up significantly.

Step 4: Create Key-Value Pairs in GAM

Prebid.js passes bid information to GAM through key-value pairs set on the GPT ad slots. GAM needs to know about these keys before it can use them for targeting.

Required Key-Value Pairs

Navigate to Inventory > Key-values in your GAM account and create the following keys:

Send All Bids vs. Top Bid Only

If you set enableSendAllBids: true in your Prebid config, you also need bidder-specific keys like hb_pb_appnexus, hb_pb_rubicon, etc. This sends every bidder price to GAM, allowing you to create bidder-specific line items. However, this dramatically increases the number of line items needed (multiply by number of bidders) and is generally not recommended unless you have specific reporting or optimization needs.

The default recommendation is enableSendAllBids: false, which sends only the top bid values to the standard hb_pb, hb_adid, and hb_bidder keys.

Step 5: Create Line Items in GAM

This is the most labor-intensive step. You need to create one line item for each price bucket in your granularity setting, all targeting the corresponding hb_pb value.

Line Item Configuration

For each price bucket, create a line item with these settings:

Targeting Setup

Each line item targets a specific hb_pb value:

Creative Setup

Unlike standard line items where you upload a specific creative, Prebid line items use a universal creative snippet that tells Prebid to render the winning bid creative. Create a single Third-Party creative with this code:

<script src="https://cdn.jsdelivr.net/npm/prebid-universal-creative@latest/dist/creative.js"></script>
<script>
  var ucTagData = {};
  ucTagData.adServerDomain = "";
  ucTagData.pubUrl = "%%PATTERN:url%%";
  ucTagData.targetingMap = %%PATTERN:TARGETINGMAP%%;
  ucTagData.hbPb = "%%PATTERN:hb_pb%%";

  try {
    ucTag.renderAd(document, ucTagData);
  } catch (e) {
    console.log("Error rendering Prebid creative: ", e);
  }
</script>

Attach this creative to every Prebid line item. GAM macros (the %%PATTERN:...%% tokens) dynamically insert the targeting values at serve time, allowing the universal creative to fetch and render the correct winning bid from Prebid cache.

Automating Line Item Creation

Creating 200+ line items manually is impractical. Use one of these tools:

Step 6: Wire It All Together

Now connect the Prebid auction to your GPT ad slots. This is the orchestration code that runs the header bidding auction and passes results to GAM:

var PREBID_TIMEOUT = 1200;  // milliseconds

pbjs.que.push(function() {
  pbjs.addAdUnits(adUnits);

  pbjs.requestBids({
    bidsBackHandler: function() {
      sendAdserverRequest();
    },
    timeout: PREBID_TIMEOUT
  });
});

// Failsafe: send GAM request even if Prebid times out
setTimeout(function() {
  sendAdserverRequest();
}, PREBID_TIMEOUT + 100);

var adserverRequestSent = false;
function sendAdserverRequest() {
  if (adserverRequestSent) return;
  adserverRequestSent = true;

  googletag.cmd.push(function() {
    pbjs.que.push(function() {
      pbjs.setTargetingForGPTAsync();
      googletag.pubads().refresh();
    });
  });
}

What This Code Does

  1. Adds the ad units to Prebid with their bidder configurations
  2. Triggers the auction with requestBids(), which sends bid requests to all configured demand partners simultaneously
  3. Waits for responses up to the PREBID_TIMEOUT value (1,200ms in this example)
  4. Calls sendAdserverRequest() once bids come back (or on timeout)
  5. Sets targeting on GPT slots using setTargetingForGPTAsync(), which copies the winning bid key-values (hb_pb, hb_adid, hb_bidder) onto the corresponding GPT slot
  6. Refreshes GPT to send the ad request to GAM with the Prebid targeting attached

The failsafe setTimeout ensures that GAM requests are sent even if Prebid.js fails to load or all bidders time out. Without this, a Prebid failure would mean no ads load at all.

GPT Slot Definition

Your GPT slot definitions should use disableInitialLoad() to prevent GAM from requesting ads before Prebid has finished its auction:

googletag.cmd.push(function() {
  googletag.defineSlot('/12345678/leaderboard', [[728, 90], [970, 250]], 'div-leaderboard')
    .addService(googletag.pubads());

  googletag.defineSlot('/12345678/sidebar', [[300, 250], [300, 600]], 'div-sidebar')
    .addService(googletag.pubads());

  googletag.pubads().disableInitialLoad();
  googletag.pubads().enableSingleRequest();
  googletag.enableServices();
});

The disableInitialLoad() call is critical. Without it, GPT would fire ad requests immediately on enableServices(), before Prebid targeting is set. Your Prebid bids would never reach GAM.

Step 7: Test with Prebid Debugging Tools

Before going live, verify that the entire flow works correctly. Prebid provides built-in debugging tools.

Enable Debug Mode

Add ?pbjs_debug=true to your page URL or run this in the browser console:

pbjs.setConfig({ debug: true });

This logs detailed information to the browser console about every step of the auction: bid requests sent, responses received, bid prices, timeouts, and targeting set on GPT slots.

Inspect Bid Responses

In the browser console, run these commands to inspect the auction results:

// See all bid responses
pbjs.getBidResponses();

// See only winning bids
pbjs.getHighestCpmBids();

// See targeting set on each ad unit
pbjs.getAdserverTargeting();

// See Prebid configuration
pbjs.getConfig();

getBidResponses() shows you every bid that came back, including the bidder name, CPM, creative size, and response time. This is where you verify that your bidder parameters are correct and that bids are actually coming in.

getAdserverTargeting() shows the key-values that were (or will be) set on GPT slots. Verify that hb_pb values match the price buckets in your granularity setting and that hb_adid and hb_bidder are populated.

GAM Delivery Diagnostics

On the GAM side, use the built-in delivery diagnostics to verify that your Prebid line items are being selected:

  1. Open the Google Publisher Console by appending ?googfc to your page URL
  2. Click on an ad slot to see which line items competed and which one won
  3. Verify that Prebid line items appear in the list with the correct CPM
  4. Check that the hb_pb targeting value matches the winning line item rate

If a Prebid line item is not showing up in the Google Publisher Console even though getAdserverTargeting() shows the correct values, the issue is likely in your GAM configuration: the key-value might not be created, the line item targeting might be wrong, or the creative might not be attached.

Common Issues and Troubleshooting

These are the problems publishers encounter most frequently when setting up Prebid.js with GAM.

No Bids Returned

If pbjs.getBidResponses() returns empty objects for all ad units:

Bids Coming In But Not Winning in GAM

If Prebid shows bids in getBidResponses() but your Prebid line items never serve:

Timeout Too Low

Symptoms: you get bids from some bidders but not others, and the missing bidders are consistently the same ones.

Wrong Creative Rendering

If the Prebid line item wins but the ad slot shows a blank or broken creative:

Advanced: Server-Side vs. Client-Side Header Bidding

The setup described above is client-side header bidding, where the user browser communicates directly with each demand partner. This is the standard approach, but it has limitations that server-side header bidding addresses.

Client-Side Header Bidding

How it works: The browser loads each bidder adapter JavaScript, which makes HTTP requests to the bidder endpoint. All of this happens in the user browser.

Advantages:

Disadvantages:

Server-Side Header Bidding (Prebid Server)

How it works: The browser sends a single request to a Prebid Server instance (hosted by you or a vendor). The server fans out bid requests to all demand partners simultaneously and returns the results to the browser in one response.

Advantages:

Disadvantages:

Hybrid Approach (Recommended)

Most publishers at scale use a hybrid approach: run your top 3-5 highest-revenue bidders client-side (where match rates matter most) and move the remaining bidders to server-side (where they add incremental competition without impacting page speed). This maximizes both revenue and performance.

To configure hybrid in Prebid.js, you specify some bidders as client-side adapters and route others through the s2sConfig:

pbjs.setConfig({
  s2sConfig: {
    accountId: 'your-prebid-server-account',
    enabled: true,
    bidders: ['openx', 'pubmatic', 'sovrn'],  // these run server-side
    timeout: 500,
    adapter: 'prebidServer',
    endpoint: 'https://prebid-server.example.com/openrtb2/auction'
  }
});

// AppNexus, Rubicon, IX still run client-side via their adapters
// OpenX, PubMatic, Sovrn run through Prebid Server

Latency Comparison

In typical setups, client-side auctions with 5 bidders complete in 800-1,200ms. The same 5 bidders via Prebid Server complete in 300-600ms. A hybrid approach with 3 client-side and 7 server-side bidders (10 total) typically completes in 900-1,300ms, adding 5 more bidders at roughly the same latency cost as the original 5 client-side bidders alone.

Optimization Tips After Launch

Once your Prebid.js + GAM integration is live and verified, focus on these optimizations to maximize revenue:

Frequently Asked Questions

What is Prebid.js and why should I use it with Google Ad Manager?

Prebid.js is an open-source header bidding wrapper that allows multiple demand sources to bid on your ad inventory simultaneously before your ad server decides which ad to serve. When integrated with GAM, Prebid sends the winning bid price as a key-value pair, which then competes against GAM own demand (AdX, AdSense) and direct-sold campaigns. This unified auction typically increases publisher revenue by 20-40% compared to running GAM alone.

How many line items do I need to create in GAM for Prebid.js?

It depends on your price granularity. Medium granularity ($0.10 increments up to $20) requires about 200 line items. High granularity ($0.01 increments) requires about 2,000. Low granularity ($0.50 increments) requires about 40. Most publishers start with medium as a practical balance. Tools like Prebid Line Item Manager can automate creation.

What timeout should I set for Prebid.js auctions?

Start with 1,000-1,500 milliseconds for client-side header bidding. Too low (under 800ms) means bidders cannot respond in time, reducing competition. Too high (over 2,000ms) delays ads and hurts user experience. Monitor bidder response times and adjust. Server-side setups can use 400-800ms since the server handles communication more efficiently.

Can I use Prebid.js with AdSense instead of Google Ad Manager?

No. Prebid.js requires GAM because it relies on key-value targeting, which AdSense does not support. However, GAM Small Business is free for publishers under 200 million monthly impressions. You can migrate to GAM and still run AdSense demand as a line item within GAM while adding Prebid header bidding on top.

What is the difference between client-side and server-side header bidding in Prebid?

Client-side runs bidder auctions in the user browser, giving bidders direct access to user cookies for better targeting but adding page weight. Server-side sends a single request to a Prebid Server that contacts bidders on the backend, reducing browser load but lowering match rates by 10-30%. Most publishers use a hybrid: top bidders client-side for match rates, remaining bidders server-side for performance.

Skip the Setup Complexity

WeForAds handles your entire header bidding stack: Prebid.js configuration, GAM line items, bidder optimization, and ongoing yield management. One tag, maximum revenue.

Get Started Free
Weekly newsletter

Get these insights in your inbox

One tactical ad monetization tip per week. No fluff. Unsubscribe anytime.

Free forever. No spam. Learn more