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 controlDatastudio 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 clientsCache tab → rule → Cache-Control Handling → To Client: respect
Custom Cache Key — ignore query stringCache tab → Global Cache Settings → Query-String Normalization
Custom Cache Key — drop UTM paramssame — dropParams chip list
Custom Cache Key — vary by cookie valueCache tab → rule → Cache Key → + Vary by cookie
Custom Cache Key — vary by header valueCache tab → rule → Cache Key → + Vary by header
Custom Cache Key — vary by single query argCache 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 defaultsGlobal 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/client Cache-Control directives.
  • ignore — strip them from the cache decision; the rule's validResponses TTL governs storage.
  • merge (upstream only) — use upstream max-age only when it is shorter than the rule TTL. The Merge Max-Age Ceiling input clamps the upstream value from above (e.g. 30s to 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 Enabled from 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 their inactiveTime evicts 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.

  1. Routes tab — leave the cache_proxy route to / in place.
  2. 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
    • 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)

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. merge mode and the Skip Store condition on $http_authorization are independent mechanisms layered on the same request. merge continues to honor origin Cache-Control: private / no-store directives even when you cap max-age; the Skip Store condition fires regardless of what the origin sends. If your origin already emits Cache-Control: private for authenticated requests, the explicit Skip Store on $http_authorization is partially redundant but still recommended as belt-and-braces — it protects against an origin regression that omits private on a logged-in response.

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):

  1. Open the rule's Cache Behavior card.
  2. 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. the X-Country header becomes $http_x_country).
    • + Vary by query arg — appends $arg_<name> for a single query parameter.
  3. 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_id or $http_user_agent directly, 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...":

PresetUse whenEffect summary
StaticMarketing site, docs, blog, file CDN — everything is cacheable.Long static-files TTL (30d), ignore upstream/client Cache-Control, normalize query strings (drop UTMs + sort).
DynamicApp 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.
MixedE-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_session for 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:

  • upstreamCacheControl is not one of inherit | respect | ignore | merge (rule C14)
  • clientCacheControl is merge (rule C15 — merge is upstream-only)
  • A directive in respect[] or ignore[] is not in the known set (rule C16)
  • The same directive is in both respect[] and ignore[] (rule C17)
  • upstreamCacheControlMaxAge is not a valid nginx-time string like 30s, 5m, 1h (rule C18)
  • A bypassConditions / noStoreConditions variable does not match ^\$[a-z_][a-z0-9_]*$ (rule C19)
  • Global upstreamCacheControlMaxAge is malformed (rule G1)
  • Global queryNormalization.dropParamsRegex does 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 into generator.ts.
  • docs/edgeResource.jsonc — annotated reference for every field on the edge-resource value, including the new bypass, upstreamCacheControlMaxAge, and queryNormalization fields.