Stored requests in Prebid Server, explained
Hardcoding bidders and params into every page, AMP tag and app build means every change needs a release. Prebid Server (PBS) solves this with stored requests: blocks of OpenRTB JSON kept on the server and merged into incoming requests by ID. The mechanism is simple. The hard part is that PBS only reads this data, and building the system that writes it is up to you.
Versions checked for this post: PBS Go 4.11.0 and PBS Java 4.4.0 (both released 26 October 2026), and Prebid.js 11.44.0.
What are stored requests, stored imps and stored responses?
There are two kinds of stored request, plus stored responses for testing.
Top-level stored requests (Go calls them stored BidRequests) are referenced at ext.prebid.storedrequest.id. They hold request-wide settings: tmax, currency, price granularity, cache instructions, site fields. Prebid recommends not putting imp objects in them, except for AMP.
Impression-level stored requests (stored imps) are referenced at imp[].ext.prebid.storedrequest.id. They hold one slot’s formats, bidders and bidder params. Since 2020 the recommended place for bidders is imp[].ext.prebid.bidder.BIDDER, not the older imp[].ext.BIDDER.
When both are present, PBS applies the top-level one first, then the imp-level ones. Three rules matter in practice:
- The incoming request wins. PBS merges stored data using JSON Merge Patch (RFC 7386). Any field the request sends overwrites the stored one, and arrays are replaced whole. If Prebid.js sends
banner.formatfrom your ad unit sizes, those sizes replace the stored ones. - Partial data is fine. A stored imp can leave out
idand let the request fill it in. - No recursion. If a top-level stored request contains imps that reference their own stored imp IDs, those inner IDs are not resolved.
Stored responses are for QA. A storedauctionresponse on an imp returns a fixed seatbid and calls no bidder for that imp. A storedbidresponse feeds a fixed response per bidder through that bidder’s adapter code, so you test the adapter end to end. A global storedauctionresponse can override the whole response.
Why does the control plane matter?
Because Prebid does not give you one. The PBS hosting guide says it plainly:
Note that there aren’t any open source tools for populating this database. Each PBS host company establishes their own methods of populating data from their internal systems.
The Prebid Server overview repeats the point: “There are no configuration tools provided by the open source community.” And the database guide adds that “PBS never writes this data, only reads it”, and that the gap between a good host and an average one shows up in how this data is managed.
There is no fixed schema either. You write the SQL and PBS only cares about the columns it returns. So if you self-host, you are building a small product: somewhere to create sites, slots and bidder params, validate them, publish them to every region, and roll them back. Managed PBS providers sell exactly this layer.
Which backends do PBS Go and PBS Java support?
Both versions read the same JSON. Where they differ is configuration and the extra backends.
| PBS Go 4.11 | PBS Java 4.4 | |
|---|---|---|
| Files | stored_requests.filesystem (default path ./stored_requests/data/by_id) | settings.filesystem.stored-requests-dir, stored-imps-dir, stored-responses-dir |
| Database | database.connection.driver: mysql or postgres, plus database.fetcher.query | settings.database.type: mysql or postgres, plus stored-requests-query |
| HTTP | http.endpoint, http.amp_endpoint | settings.http.endpoint, amp-endpoint |
| Object storage | None | settings.s3.* |
| In-memory cache | in_memory_cache: none, unbounded or lru (default none) | settings.in-memory-cache.ttl-seconds, cache-size, jitter-seconds |
| Refresh | http_events polling, database.poll_for_updates with $LAST_UPDATED, cache_events save/invalidate API | http-update, database-update (query on last_updated), s3-update, /storedrequests/openrtb2 and /storedrequests/amp |
A few details from the source that the docs gloss over:
- Query placeholders differ. PBS Java uses
%REQUEST_ID_LIST%and%IMP_ID_LIST%. PBS Go 4.11 substitutes$REQUEST_ID_LIST,$IMP_ID_LISTand$LAST_UPDATED. Some Go examples on docs.prebid.org still show the older%...%form and apostgres:block directly understored_requests; the current Go config nests it underdatabase.connectionanddatabase.fetcher. - Java scopes IDs by account. The Java query returns an account ID column first, so two publishers can use the same stored request ID. Go has no such column, so prefix your IDs per site or account.
- Go caches nothing by default. With
in_memory_cache.type: none, every lookup goes to the backend, and Go’s config validation rejects refresh polling orcache_eventswithout a cache. - The HTTP backend is a contract you implement. Go calls
GET {endpoint}?request-id=...&imp-id=...(RFC 3986 style is the default) and expects{"requests": {...}, "imps": {...}}back, withnullfor unknown IDs. This is often the easiest way to put your own control plane behind PBS. - Lock down the write endpoints. Go’s events API accepts
POSTto save andDELETEto invalidate cache entries, and its source warns it “should not be exposed on a public network without authentication”. Java’s equivalents sit underadmin-endpointswith aprotectedflag.
How do Prebid.js, AMP and Prebid Mobile reference a stored request?
Prebid.js. Put the ID in ortb2Imp and leave out bids. If s2sConfig.bidders is also omitted, only ad units without bidders go to PBS.
pbjs.addAdUnits([{
code: 'home-top-mpu',
mediaTypes: { banner: { sizes: [[300, 250], [336, 280]] } },
ortb2Imp: {
ext: { prebid: { storedrequest: { id: 'site1-home-top-mpu' } } }
}
}]);
Prebid.js copies s2sConfig.accountId into site.publisher.id, which is how PBS finds the account. You can point to a top-level stored request through s2sConfig.extPrebid, since its contents are merged into ext.prebid: extPrebid: { storedrequest: { id: 'site1-defaults' } }.
AMP. The tag_id on /openrtb2/amp must reference a top-level stored request containing exactly one imp. AMP query parameters (w, h, slot, curl, timeout and others) overwrite parts of it. In rtc-config, the prebidrubicon vendor takes REQUEST_ID and prebidappnexuspsp takes PLACEMENT_ID; both become tag_id. Pass account if your tag IDs are not unique across accounts. PBS caps the timeout at the smaller of the AMP default (1000 ms) and the timeoutMillis in rtc-config.
Prebid Mobile. The config ID on each ad unit becomes the imp-level stored request ID. prebidServerAccountId becomes app.publisher.id and, unless you set auctionSettingsId, also the top-level stored request ID. auctionSettingsId lets each app carry its own top-level settings under one account.
A small, verified example
This stored imp works on both servers. For PBS Go’s file backend it lives at stored_requests/data/by_id/stored_imps/site1-home-top-mpu.json; for PBS Java, in the directory set by stored-imps-dir.
{
"banner": {
"format": [
{ "w": 300, "h": 250 },
{ "w": 336, "h": 280 }
]
},
"ext": {
"prebid": {
"bidder": {
"msft": { "placement_id": 12345678 },
"pubmatic": { "publisherId": "156209" }
}
}
}
}
The params match the bidder schemas shipped with PBS Go 4.11 and PBS Java 4.4: msft needs an integer placement_id (or inv_code plus member), and pubmatic needs a string publisherId. The values here are placeholders, so swap in your own.
A matching top-level stored request for site-wide settings:
{
"tmax": 800,
"ext": {
"prebid": {
"targeting": {
"pricegranularity": {
"precision": 2,
"ranges": [{ "max": 20.00, "increment": 0.10 }]
}
}
}
}
}
Account config
Stored requests describe the auction. Account config describes the publisher: privacy enforcement, cache TTLs, events, cookie sync limits, modules and floors. PBS finds the account ID in site.publisher.id, then app.publisher.id, then the AMP account parameter.
- PBS Go merges host-level
account_defaultswith per-account data from theaccountssection (files underaccounts/or an HTTP endpoint returning{"accounts": {...}}).account_requireddefaults tofalse. - PBS Java takes most account settings from the database via
settings.database.account-query, or from files and S3.settings.enforce-valid-accountdefaults tofalse; whentrue, requests without an account ID are rejected with a 401.
Turn on account enforcement in production so unknown accounts cannot use your cluster.
Practical tips
Keep one source of truth, and version it. Store site, slot and bidder config once, and generate the stored imps, top-level requests, AMP requests and account rows from it. Because stored data is not resolved recursively, an AMP stored request cannot point at your stored imps; it must inline its bidders, so generate it from the same source instead of copying by hand. Keep history (Prebid’s database guide suggests adding insert and update dates to its sample tables) so you can roll back by republishing the previous version.
Keep IDs stable. Changing an ID means changing the page, the AMP tag or the app. Change the content behind the ID instead, and prefix IDs by site or account if you run PBS Go.
Validate before you publish. PBS rejects the whole request when any bidder’s params are invalid. Check every stored imp against the JSON schemas in static/bidder-params for your PBS version before it goes live.
Size the cache to your change window. A TTL plus a refresh job (poll_for_updates in Go, database-update in Java) bounds how long a change takes to go live. Use the invalidation endpoints for urgent fixes, and keep them behind authentication.
Use stored responses for QA. A storedauctionresponse ID on a test ad unit gives you a predictable bid for checking rendering, targeting and creative setup without waiting on demand.
Checklist
- Decide where stored data lives: files, MySQL or Postgres, HTTP, or S3 (Java only).
- Turn on the in-memory cache and a refresh method. Go’s default is no cache.
- Use the right query placeholders for your server (
$...in Go,%...%in Java). - Build or buy the control plane that writes, validates, versions and publishes the data.
- Generate AMP stored requests with exactly one inlined imp.
- Enforce valid accounts and protect every write or invalidation endpoint.
Frequently asked questions
What is the difference between a stored request and a stored impression?
A top-level stored request is referenced at ext.prebid.storedrequest.id and holds request-wide settings such as tmax and targeting. A stored impression is referenced at imp[].ext.prebid.storedrequest.id and holds one slot's formats, bidders and params.
What happens if the page and the stored request disagree?
Prebid Server merges them with JSON Merge Patch (RFC 7386), and the incoming HTTP request wins on any conflicting field. Arrays such as banner.format are replaced, not combined.
Does Prebid provide a tool to manage stored requests?
No. Prebid's hosting guide says there aren't any open-source tools for populating the database, and each host builds its own. Managed Prebid Server providers usually supply a UI for this.
How does AMP use stored requests?
The tag_id parameter on /openrtb2/amp must point to a top-level stored request containing exactly one imp. AMP query parameters such as size, slot and page URL then overwrite parts of it.
What are stored responses for?
Testing and debugging. A storedauctionresponse returns a fixed response for an impression without calling any bidder, and a storedbidresponse feeds a fixed response through a bidder's adapter code.

About the author
Founder, BidCliq
Sharad Yadav is the founder of BidCliq, a publisher-first ad monetization platform that helps publishers and ad ops teams run header bidding, Prebid and programmatic across display, video, app and CTV. Before BidCliq, he led AdTech and product at GeeksforGeeks, where he built an in-house ads product that doubled programmatic revenue within nine months and an anti-adblock solution that lifted ad yield by 10%. He is a founding member of AdCP.