Handling Dynamic Requests at the Edge
This guide is for operators migrating from Cloudflare Cache Rules to OpenCache, or modeling new edge-resource policies in Datastudio. It maps the Cloudflare vocabulary onto the controls exposed by the Routes & Caching tab of the Edge Resource detail view.
OpenCache has no single "is this dynamic?" flag. Instead, dynamic semantics are composed from a handful of primitives applied in a fixed precedence chain. Knowing where each primitive fits lets you recreate any Cloudflare Cache Rule + Cache Eligibility configuration without surprise.
Quick start
If you're just starting out and want a sensible baseline rather than configuring
fields one-by-one, open Routes & Caching → Cache → Global Cache Settings → "Apply preset..."
and pick Static, Dynamic, or Mixed. Each preset shows a diff of every
field it will change before you commit. The preset only touches site-global
settings; per-rule configuration is preserved. See Site profile presets below.
TL;DR — Cloudflare → Datastudio
| Cloudflare control | Datastudio control |
|---|---|
| Cache Rule action "Bypass cache" (URL prefix) | Routes tab → Action proxy |
| Cache Rule action "Bypass cache" (per match) | Cache tab → rule → Cache Behavior → Bypass Cache toggle |
| Cache Eligibility → "Bypass cache on Cookie" | Cache tab → rule → Bypass Lookup condition ($cookie_<name>) |
| Cache Eligibility → "Bypass cache for logged in users" | Cache tab → rule → Bypass Lookup ($http_authorization) |
| "Cache deception armor" (don't store auth'd responses) | Cache tab → rule → Skip Store condition ($http_authorization) |
| Page Rules → "Respect Existing Headers" | Cache tab → rule → Cache-Control Handling → From Upstream: respect |
| Page Rules → "Override origin Cache-Control" | Cache tab → rule → Cache-Control Handling → From Upstream: ignore |
Cap upstream max-age (custom origin TTL clamp) | Cache tab → rule → Cache-Control Handling → From Upstream: merge + Merge Max-Age Ceiling |
Honor Cache-Control: no-cache from clients | Cache tab → rule → Cache-Control Handling → To Client: respect |
| Custom Cache Key — ignore query string | Cache tab → Global Cache Settings → Query-String Normalization |
| Custom Cache Key — drop UTM params | same — dropParams chip list |
| Custom Cache Key — vary by cookie value | Cache tab → rule → Cache Key → + Vary by cookie |
| Custom Cache Key — vary by header value | Cache tab → rule → Cache Key → + Vary by header |
| Custom Cache Key — vary by single query arg | Cache tab → rule → Cache Key → + Vary by query arg |
| Cloudflare APO "Bypass for logged in WordPress users" | Global Cache Settings → Known Static Files → Bypass When Authenticated |
| One-click sensible defaults | Global Cache Settings → Apply preset... (Static / Dynamic / Mixed) |
The five layers (highest precedence first)
1. Route action — coarse URL-prefix bypass
Use this when an entire URL prefix is dynamic and should never see the cache.
- Open Routes & Caching → Routes.
- Add a route. Pick a match (Prefix
/api/, Regex^/admin/, Exact/healthz, etc.). - Set Action to
proxy. The Datastudio UI shows an amber tip explaining this is the Cloudflare "Bypass cache" equivalent. - Select an upstream.
When Action is proxy, the generated nginx location block has no
proxy_cache directive at all — it's a vanilla reverse proxy. There is no
way for the request to enter the cache from this route. Use this when the
URL alone tells you the response is dynamic.
If you want to disable a route temporarily without deleting it, flip the Enabled switch off — the route is omitted from the generated config.
2. Per-rule Bypass Cache — fine-grained URL/match bypass
Use this when the same URL must serve cacheable AND dynamic responses depending on cookie, header, or query state — for example a marketing site that shows a logged-in version on the same path.
- Open Routes & Caching → Cache Rules.
- Add a rule with the conditions that select the dynamic case
(e.g.
Cookie session exists). - In the rule's Cache Behavior card, flip Bypass Cache on.
When Bypass Cache is on, the rule still matches and other rule-level metadata still applies (valid-responses, headers, expiration), but the response always goes upstream and is never served from cache. The Cache-Control Handling and Runtime Bypass Conditions subsections grey out — they don't apply when the entire rule is bypassed.
3. Runtime bypass — bypassConditions[] and noStoreConditions[]
Use this when bypass should depend on runtime request state (cookies, headers, query args) and you don't want a separate rule per state.
In the rule's Runtime Bypass Conditions subsection there are two lists:
- Bypass Lookup — emits
proxy_cache_bypass <variable>;. The request goes upstream, but the response may still be stored. This is right when the bypass is about freshness (force a fresh fetch with?nocache=1) rather than secrecy. - Skip Store — emits
proxy_no_cache <variable>;. The response is fetched upstream and never stored. This is right when the response is per-user (logged-in pages, personalized content) and storing it would poison the cache for other visitors.
Each row has an Variable input that autocompletes from common nginx
variables: $http_authorization, $cookie_session, $arg_nocache, etc.
The Variable validates as ^\$[a-z_][a-z0-9_]*$ (an nginx variable spec).
The optional Operator and Value inputs are reserved for a future
njs-based evaluator and have no runtime effect today — Datastudio
shows a warning when you expand the Advanced disclosure.
Why both lists? A bypassed-but-stored response still warms the cache for the next visitor. A no-store response is fetched and discarded. If a logged-in user requests
/, you usually want both: bypass the cache (so they see their personalized version) AND skip storing the response (so the next anonymous visitor doesn't see their account).
4. Origin Cache-Control honoring — four modes per rule
Use this to decide how much trust to put in the Cache-Control headers
the origin sends back.
In the rule's Cache-Control Handling card, two selects:
- From Upstream —
inherit|respect|ignore|merge - To Client —
inherit|respect|ignore
Modes:
inherit— fall through to the site-global toggle (Global Cache Settings → Respect Upstream/Client Cache-Control).respect— honor the upstream/clientCache-Controldirectives.ignore— strip them from the cache decision; the rule'svalidResponsesTTL governs storage.merge(upstream only) — use upstreammax-ageonly when it is shorter than the rule TTL. The Merge Max-Age Ceiling input clamps the upstream value from above (e.g.30sto ignore origin TTLs longer than 30 seconds even when they're "shorter" than your rule TTL).
When From Upstream is respect or ignore, a chip-picker appears
showing the seven known directives:
no-store, no-cache, private, must-revalidate, proxy-revalidate,
s-maxage, max-age. Click chips to add to the respect or ignore
list. The same chip cannot be in both lists — clicking it on one side
auto-removes it from the other (validation rule C17).
Site-global ceiling. Global Cache Settings → Merge Max-Age Ceiling sets the default for any rule with
upstreamCacheControl: "merge"that doesn't specify its own. Per-rule beats global.
5. Known-static-files auth bypass — APO-style shortcut
If you've enabled Cache Known Static Files (the auto-static catch-all),
Global Cache Settings exposes a single Bypass When Authenticated switch.
When on, requests with an Authorization header skip the auto-static cache
even if the URL extension matches. This is the Cloudflare APO "bypass for
logged-in users" equivalent for the static catch-all.
This switch does not apply to your explicit Cache Rules — they get their own per-rule Bypass Lookup configuration as in §3.
Query-string normalization — Custom Cache Key
Cloudflare's "Custom Cache Key" with "Ignore Query String" lets you collapse
/page?utm_source=x and /page?utm_source=y into one cache entry. Datastudio
exposes the same via Global Cache Settings → Query-String Normalization:
- Enabled master switch.
- Drop Params — chip list of exact parameter names to remove
(
utm_source,fbclid,gclid, etc.). - Drop Params Regex — regex matching parameter names to remove
(
^_ga|^utm_). Regex compile errors surface inline in the form. - Sort Params — alphabetize the surviving params before keying.
Cache invalidation warning. Flipping
Enabledfrom off to on on a live site changes the cache key for every request that carries query params. Previously-cached entries are orphaned — they remain on disk until theirinactiveTimeevicts them, but they will not be served. Datastudio shows an amber warning when you enable normalization on an existing resource. Treat this as a destructive cache invalidation.
End-to-end example — "marketing site with auth'd version"
Goal: serve the marketing homepage from cache for anonymous visitors; always render fresh + never store for logged-in visitors; let the origin control TTLs but never cache for longer than 1 hour.
- Routes tab — leave the
cache_proxyroute to/in place. - Cache Rules tab → add a rule:
- Match: URI
equals/ - Cache Behavior:
- Cache Enabled: on
- Bypass Cache: off
- Zone:
default - Valid Responses:
200 → 1h
- Cache-Control Handling:
- From Upstream:
merge - Merge Max-Age Ceiling:
1h - To Client:
ignore
- From Upstream:
- Runtime Bypass Conditions:
- Bypass Lookup:
$cookie_session,$http_authorization - Skip Store:
$cookie_session,$http_authorization,$upstream_http_set_cookie(don't poison the anonymous cache with a Set-Cookie response)
- Bypass Lookup:
- Match: URI
That's the entire policy. The same effect in Cloudflare requires three separate Cache Rules and at least one Page Rule.
A note on additivity.
mergemode and the Skip Store condition on$http_authorizationare independent mechanisms layered on the same request.mergecontinues to honor originCache-Control: private/no-storedirectives even when you capmax-age; the Skip Store condition fires regardless of what the origin sends. If your origin already emitsCache-Control: privatefor authenticated requests, the explicit Skip Store on$http_authorizationis partially redundant but still recommended as belt-and-braces — it protects against an origin regression that omitsprivateon a logged-in response.
Vary cache by request property — Custom Cache Key by cookie / header / query arg
Cloudflare's "Custom Cache Key" with cookie or header inclusion lets one URL serve multiple cached entries — one per unique cookie/header value. Common uses:
$cookie_country→ serve a UK-localized page from one cache entry, an IT-localized page from another, all from the same URL.$http_x_user_tier→ serve free-tier and pro-tier dashboards from one URL but cache them as separate entries.$cookie_theme→ light-mode and dark-mode HTML cached separately.$arg_variant→ an A/B test variant served from the same URL but cached per-variant.
Datastudio exposes this through the Cache Key builder on every rule (and on routes via the Cache Key field):
- Open the rule's Cache Behavior card.
- Find the Cache Key field. Below the existing toggle pills, three add buttons:
- + Vary by cookie — appends
$cookie_<name>to the cache key. - + Vary by header — appends
$http_<name>(use lowercase + underscores; e.g. theX-Countryheader becomes$http_x_country). - + Vary by query arg — appends
$arg_<name>for a single query parameter.
- + Vary by cookie — appends
- Click an add button, then type the cookie/header/arg name in the input that appears. The cache key preview at the top updates live.
Cardinality warning. Every distinct value of the chosen variant produces a separate cache entry. Choose variants whose value space is small and bounded (e.g. 5 country codes, 3 user tiers, 2 themes) — never vary by an unbounded value like
$cookie_session_idor$http_user_agentdirectly, or you'll DoS your own keys_zone.
Variant + bypass interaction. Varying by cookie does NOT bypass the cache — it splits it. If you want the cookie to bypass entirely instead of producing a cached variant, use Bypass Lookup in §3 above.
For query-string customization at the site level (drop UTMs, sort params), see Query-String Normalization in Global Cache Settings — that's the "strip params I don't care about" half of Cloudflare's Custom Cache Key, complementary to the Vary buttons here.
Site profile presets
Three one-click presets bulk-apply known-good defaults to the site-global cache settings. Open Cache → Global Cache Settings → "Apply preset...":
| Preset | Use when | Effect summary |
|---|---|---|
| Static | Marketing site, docs, blog, file CDN — everything is cacheable. | Long static-files TTL (30d), ignore upstream/client Cache-Control, normalize query strings (drop UTMs + sort). |
| Dynamic | App with logged-in users, personalized content, dashboards. | Trust the origin (respect upstream + client Cache-Control), cap upstream max-age at 1h, no query normalization, static files bypass on auth. |
| Mixed | E-commerce, SaaS marketing + app, CMS-heavy site. | Respect upstream Cache-Control with 1h ceiling, drop UTMs + sort params, static files bypass on auth. |
Each preset shows a field-level diff (path, before, after) before you apply, so you can see exactly what will change. Per-rule configuration is never touched by presets — you keep your existing rules. The Apply button is disabled when the current settings already match the chosen preset (so re-running a preset on a freshly-configured site is a no-op).
About the Dynamic preset. It can't add a per-rule bypass on
$cookie_sessionfor you — the preset only edits site-global settings. The dialog shows a yellow "Recommended next steps" hint pointing operators at the bypass-cookie pattern in §3. Add the rule manually after applying the preset.
Where the validator catches mistakes
Save fails with a field-level error when:
upstreamCacheControlis not one ofinherit | respect | ignore | merge(rule C14)clientCacheControlismerge(rule C15 —mergeis upstream-only)- A directive in
respect[]orignore[]is not in the known set (rule C16) - The same directive is in both
respect[]andignore[](rule C17) upstreamCacheControlMaxAgeis not a valid nginx-time string like30s,5m,1h(rule C18)- A
bypassConditions/noStoreConditionsvariable does not match^\$[a-z_][a-z0-9_]*$(rule C19) - Global
upstreamCacheControlMaxAgeis malformed (rule G1) - Global
queryNormalization.dropParamsRegexdoes not compile (rule Q1)
The errors land on the affected field path and block save until cleared.
Further reading
opencache/config-generator/README.md§Dynamic Request Handling — generator-side mapping to nginx directives, with file/line references intogenerator.ts.docs/edgeResource.jsonc— annotated reference for every field on the edge-resource value, including the newbypass,upstreamCacheControlMaxAge, andqueryNormalizationfields.
