/find

Stored requests in Prebid Server, explained

Sharad Yadav
Founder, BidCliq

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.format from your ad unit sizes, those sizes replace the stored ones.
  • Partial data is fine. A stored imp can leave out id and 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.11PBS Java 4.4
Filesstored_requests.filesystem (default path ./stored_requests/data/by_id)settings.filesystem.stored-requests-dir, stored-imps-dir, stored-responses-dir
Databasedatabase.connection.driver: mysql or postgres, plus database.fetcher.querysettings.database.type: mysql or postgres, plus stored-requests-query
HTTPhttp.endpoint, http.amp_endpointsettings.http.endpoint, amp-endpoint
Object storageNonesettings.s3.*
In-memory cachein_memory_cache: none, unbounded or lru (default none)settings.in-memory-cache.ttl-seconds, cache-size, jitter-seconds
Refreshhttp_events polling, database.poll_for_updates with $LAST_UPDATED, cache_events save/invalidate APIhttp-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_LIST and $LAST_UPDATED. Some Go examples on docs.prebid.org still show the older %...% form and a postgres: block directly under stored_requests; the current Go config nests it under database.connection and database.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 or cache_events without 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, with null for 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 POST to save and DELETE to invalidate cache entries, and its source warns it “should not be exposed on a public network without authentication”. Java’s equivalents sit under admin-endpoints with a protected flag.

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_defaults with per-account data from the accounts section (files under accounts/ or an HTTP endpoint returning {"accounts": {...}}). account_required defaults to false.
  • PBS Java takes most account settings from the database via settings.database.account-query, or from files and S3. settings.enforce-valid-account defaults to false; when true, 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.

Sharad Yadav

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.