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:
- Google Ad Manager account: You need GAM (not AdSense) because Prebid.js relies on key-value targeting to pass bid prices to the ad server. The free GAM Small Business tier works. If you are currently on AdSense only, you will need to migrate to GAM first.
- Ad units created in GAM: You should already have ad units defined for the placements where you want to run header bidding (for example, a 300x250 sidebar unit, a 728x90 leaderboard, a 320x50 mobile banner). Each ad unit needs a unique code that you will reference in your Prebid configuration.
- Bidder accounts: Sign up with at least 2-3 SSPs or ad exchanges that offer Prebid.js adapters. Common choices include AppNexus (Xandr), Rubicon Project (Magnite), Index Exchange, OpenX, PubMatic, and Sovrn. Each partner will provide you with account credentials and placement IDs.
- GPT (Google Publisher Tag) already on your page: Your page should already be loading the Google Publisher Tag library and defining ad slots. Prebid.js integrates with GPT, not replaces it.
- Basic JavaScript knowledge: You will be adding and configuring JavaScript on your pages. Familiarity with async script loading and ad tag implementation is helpful.
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:
- dfpAdServerVideo (if you run video ads)
- consentManagement (for GDPR/CCPA compliance)
- priceFloors (if you want to set floor prices)
- currency (if your bidders respond in different currencies)
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
- The
codefield must match your GPT slot div ID exactly. If your page has<div id="div-leaderboard"></div>, the code must be'div-leaderboard'. - Each bidder has unique parameter requirements. AppNexus uses a single
placementId. Rubicon requiresaccountId,siteId, andzoneId. Index Exchange requiressiteIdandsize. Always check the bidder documentation on prebid.org for the exact parameters. - Include multiple sizes per ad unit where applicable. A 300x250 slot that also accepts 300x600 will receive more bids, increasing competition and revenue.
- Start with 3-5 bidders. Adding too many bidders (10+) increases page weight and latency without proportional revenue gains. The first 3-5 quality bidders capture the majority of the revenue uplift.
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:
hb_pb(Header Bidding Price Bucket): The rounded bid price. This is the most important key. Values will be strings like "1.50", "2.00", "0.10". Set the value type to "Free form" since the values are generated dynamically.hb_adid(Header Bidding Ad ID): The unique identifier for the winning creative. Prebid uses this internally to render the correct creative when the line item wins. Set to "Free form".hb_bidder(Header Bidding Bidder): The name of the winning bidder (for example, "appnexus", "rubicon", "ix"). Useful for reporting breakdowns by bidder. Set to "Free form".hb_size(Header Bidding Size): The creative size of the winning bid (for example, "300x250"). Helps with multi-size ad units. Set to "Free form".hb_format(Header Bidding Format): The media type of the winning bid ("banner", "video", "native"). Set to "Free form".
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:
- Name: Use a clear naming convention like "Prebid_hb_pb_1.50" or "HB $1.50"
- Order: Create a single order called "Prebid Header Bidding" to hold all line items
- Type: Price Priority (this allows GAM to compare the Prebid bid against other demand)
- Rate: Set the CPM rate to match the price bucket value (for example, $1.50 CPM for the hb_pb=1.50 line item)
- Sizes: Include all ad sizes you use across your site (300x250, 728x90, 320x50, etc.)
- Start/End dates: Set start date to today, end date to "Unlimited"
- Goal: Unlimited impressions
Targeting Setup
Each line item targets a specific hb_pb value:
- Line item "Prebid $0.10" targets
hb_pb=0.10 - Line item "Prebid $0.20" targets
hb_pb=0.20 - Line item "Prebid $0.30" targets
hb_pb=0.30 - Continue for every price increment in your granularity...
- Line item "Prebid $20.00" targets
hb_pb=20.00
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:
- Prebid Line Item Manager: An official open-source tool from prebid.org that uses the GAM API to create line items automatically based on your granularity settings.
- GAM API scripts: Write a Python or Node.js script using Google Ad Manager API client library to batch-create line items programmatically.
- Third-party tools: Platforms like WeForAds handle the entire GAM setup automatically, including line item creation, creative attachment, and key-value configuration.
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
- Adds the ad units to Prebid with their bidder configurations
- Triggers the auction with
requestBids(), which sends bid requests to all configured demand partners simultaneously - Waits for responses up to the PREBID_TIMEOUT value (1,200ms in this example)
- Calls
sendAdserverRequest()once bids come back (or on timeout) - 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 - 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:
- Open the Google Publisher Console by appending
?googfcto your page URL - Click on an ad slot to see which line items competed and which one won
- Verify that Prebid line items appear in the list with the correct CPM
- Check that the
hb_pbtargeting 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:
- Check bidder parameters: Incorrect placement IDs or account IDs are the most common cause. Double-check every parameter against your SSP account dashboard.
- Check ad sizes: If you request a size that the bidder does not support for your placement, you will get no bid. Verify that the sizes in your Prebid config match what is configured on the SSP side.
- Check consent: If you have a CMP (Consent Management Platform) and the user has not consented, some bidders will not bid. Test with consent granted.
- Check network requests: Open the browser Network tab and filter for the bidder domain. Verify that bid requests are being sent and responses are returning with 200 status codes.
Bids Coming In But Not Winning in GAM
If Prebid shows bids in getBidResponses() but your Prebid line items never serve:
- Verify targeting is set: Run
pbjs.getAdserverTargeting()and confirm thathb_pbvalues are present. If they are not,setTargetingForGPTAsync()may not be executing correctly. - Check GAM key-values: Ensure the key names in GAM exactly match what Prebid sends (case-sensitive).
hb_pbin GAM must matchhb_pbfrom Prebid. - Check line item targeting: The line item for hb_pb=1.50 must target the exact string "1.50", not "1.5" or "$1.50". Prebid always uses two decimal places.
- Check line item type: Line items must be "Price Priority" type. If you accidentally created them as "Standard" or "Network" type, they will behave differently in the auction.
- Check creative attachment: Every Prebid line item must have the universal creative attached and the creative must be approved (not pending review).
Timeout Too Low
Symptoms: you get bids from some bidders but not others, and the missing bidders are consistently the same ones.
- Check bidder response times in debug mode. If a bidder consistently responds in 900-1100ms and your timeout is 800ms, those bids get dropped.
- Increase your timeout to 1200-1500ms as a starting point. Monitor the impact on page load speed and adjust.
- Consider moving slow bidders to server-side (Prebid Server) where response times are typically faster.
Wrong Creative Rendering
If the Prebid line item wins but the ad slot shows a blank or broken creative:
- Check the universal creative code: Ensure the
%%PATTERN:...%%macros are intact and not URL-encoded or escaped. - Check creative size: The GAM creative should be set to match all possible sizes, or use "Out of page" size.
- Check SafeFrame: Some Prebid creatives do not render correctly inside SafeFrames. If you use SafeFrames in GAM, you may need to adjust the creative to use postMessage-based rendering instead.
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:
- Higher match rates because bidders receive the user cookies and IP address directly, enabling better user identification and targeting
- Simpler infrastructure since no server component is needed
- Full transparency into each bidder behavior through browser dev tools
Disadvantages:
- Each bidder adapter adds JavaScript weight to the page (5-15 KB per adapter)
- Bid requests are made sequentially from the browser, increasing total latency
- Adding more than 8-10 bidders noticeably degrades page load performance
- Mobile devices with slower CPUs and connections are disproportionately affected
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:
- Minimal browser-side JavaScript since bidder adapters run on the server
- Parallel bid requests from the server reduce total auction time
- Can support 20+ bidders without impacting page performance
- Better for mobile where bandwidth and CPU are limited
Disadvantages:
- Lower match rates (typically 10-30% lower) because bidders receive the server IP and limited cookie data instead of the user data
- Requires server infrastructure (Prebid Server is a Go application) or a hosted solution
- Less transparency since bidder communication happens server-side
- Cookie syncing becomes more complex and critical for maintaining match rates
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:
- Monitor bidder performance weekly: Track each bidder win rate, average CPM, and response time. Remove or replace bidders that consistently provide low CPMs or timeout frequently.
- Implement price floors: Use the Prebid
priceFloorsmodule to set minimum bid prices. This prevents low-quality bids from winning and can actually increase average CPMs by signaling to bidders that they need to bid higher. - Enable ad refresh on high-viewability units: Sticky placements and above-the-fold units with high viewability are ideal candidates for Prebid-powered ad refresh, multiplying impressions from a single slot.
- Test timeout values: Run A/B tests with different timeouts (for example, 1,000ms vs. 1,500ms). Measure the revenue difference against the page speed impact to find your optimal balance.
- Upgrade price granularity over time: If you started with medium, consider moving to high or custom granularity once your traffic justifies the additional line items. The penny-level precision captures revenue that medium granularity rounds away.
- Add User ID modules: Prebid User ID modules (Unified ID 2.0, LiveRamp, SharedID) improve match rates by providing bidders with better user identification, which translates to higher bids.
- Analyze by geo and device: Bidder performance varies significantly by country and device type. A bidder that performs well on US desktop traffic may be weak on Indian mobile traffic. Tailor your bidder stack to your traffic profile.
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
By