Examples (13)·续2

if (request.method === "POST" && url.pathname === "/record") { // Write path — bypass the cached entrypoint, hit the Durable // Object directly, then ask CachedLeaderboard to invalidate its // own cache so the next read returns fresh data. The purge must // run inside CachedLeaderboard because purges are scoped to the // entrypoint that owns the cached response — a purge from this // gateway would target the gateway's (disabled) cache instead. const id = env.LEADERBOARD.idFromName("global"); const stub = env.LEADERBOARD.get(id); const result = await stub.fetch(request);

await ctx.exports.CachedLeaderboard.invalidate();

return result; }

return new Response("Not found", { status: 404 }); }, } satisfies ExportedHandler<Env>;


Why this works:

- **Reads pay nothing on a cache hit.** Workers Caching sits in front of `CachedLeaderboard`, so a hit returns the cached body without invoking the wrapper, without invoking the Durable Object, and without doing the expensive aggregation. The default entrypoint still runs to dispatch the request, but it is a thin router.
- **Writes invalidate the cache immediately.** The POST handler updates the Durable Object and then calls `ctx.exports.CachedLeaderboard.invalidate()`, which runs [`purge({ tags: ["leaderboard"] })`](https://developers.cloudflare.com/workers/cache/purge/#purge-by-tag) *inside* `CachedLeaderboard`. This matters because [purges are scoped to the entrypoint that calls them](https://developers.cloudflare.com/workers/cache/purge/#purge-modes) — the gateway's cache is disabled, so a purge issued from the gateway would not touch the entries `CachedLeaderboard` stored. The very next GET misses the cache, reruns the wrapper, and stores a fresh response.
- **The cached entrypoint owns the cache contract.** All cache-control headers are set in `CachedLeaderboard`, including the `Cache-Tag`, and `CachedLeaderboard` also exposes the `invalidate()` method that purges them. The Durable Object stays unaware of caching.

If you have many independent Durable Object instances — for example, one per tenant — pass the tenant identifier via `ctx.props` when invoking the cached entrypoint, the same way [Per-user authenticated responses](#per-user-authenticated-responses) does. Each tenant gets its own cache entry, and a purge on one tenant does not invalidate any other.

## Cache an origin you do not control

Sometimes the origin you depend on is not yours. A third-party API, a SaaS endpoint, a public dataset, a vendor service behind a slow CDN — its caching headers are whatever the owner decided to ship, and you cannot change them. Maybe it sends `Cache-Control: no-store` to be safe. Maybe it sends nothing at all. Maybe it caches aggressively in a way that does not match your application's read patterns. Either way, you pay the latency and the request cost on every call.

Workers Caching lets you put your own cache layer in front of that origin without changing anything on the origin side. The pattern is the same outer-plus-inner shape as the rest of this page: a thin entrypoint that forwards to the origin, with Workers Caching sitting in front of it and applying the `Cache-Control` directives you choose. The origin keeps its own caching contract with the rest of the world; your Worker just adds a second, user-controlled layer between your application and that origin. As with the other patterns, disable caching on the gateway and enable it on `CachedOrigin`:

```jsonc
{
    "name": "my-worker",
    "main": "src/index.ts",
    "upload_source_maps": true,
    // Set this to today's date
    "compatibility_date": "2026-10-27",
    "cache": { "enabled": true },
    "exports": {
        "default": { "type": "worker", "cache": { "enabled": false } },
        "CachedOrigin": { "type": "worker", "cache": { "enabled": true } },
    },
}
name = "my-worker"
main = "src/index.ts"
upload_source_maps = true
# Set this to today's date
compatibility_date = "2026-10-27"

[cache]
enabled = true

[exports.default]
type = "worker"

[exports.default.cache]
  enabled = false

[exports.CachedOrigin]
type = "worker"

[exports.CachedOrigin.cache]
  enabled = true

src/index.jsjs

import { WorkerEntrypoint } from "cloudflare:workers";

const ORIGIN = "https://api.example.com";

// Cached entrypoint. Fetches the upstream origin and overlays your own
// Cache-Control on the response. Workers Caching sits in front of this,
// so on a hit the upstream origin is never contacted.
export class CachedOrigin extends WorkerEntrypoint {
    async fetch(request) {
        const url = new URL(request.url);
        const upstream = new URL(url.pathname + url.search, ORIGIN);

// Forward the request to the third-party origin. The origin's own
        // caching headers (or lack of them) are about to be overwritten —
        // they apply to the origin's relationship with the public internet,
        // not to your cache layer.
        const response = await fetch(upstream, {
            method: request.method,
            headers: request.headers,
            body: request.body,
        });

// Replace the origin's Cache-Control with your own. This is the
        // whole point of the pattern: you decide how long Workers Caching
        // stores this response, regardless of what the origin says.
        const headers = new Headers(response.headers);
        headers.set("Cache-Control", "public, max-age=300");
        headers.set("Cache-Tag", "origin:example");

return new Response(response.body, {
            status: response.status,
            statusText: response.statusText,
            headers,
        });
    }
}

// Default entrypoint. Forwards every request through the cached entrypoint.
export default {
    async fetch(request, env, ctx) {
        // The gateway's cache is disabled (see the Wrangler configuration
        // above), so it runs on every request and forwards to the cached
        // CachedOrigin entrypoint.
        return ctx.exports.CachedOrigin.fetch(request);
    },
};

src/index.tsts

import { WorkerEntrypoint } from "cloudflare:workers";

const ORIGIN = "https://api.example.com";

// Cached entrypoint. Fetches the upstream origin and overlays your own
// Cache-Control on the response. Workers Caching sits in front of this,
// so on a hit the upstream origin is never contacted.
export class CachedOrigin extends WorkerEntrypoint {
    async fetch(request: Request): Promise&lt;Response> {
        const url = new URL(request.url);
        const upstream = new URL(url.pathname + url.search, ORIGIN);

// Forward the request to the third-party origin. The origin's own
        // caching headers (or lack of them) are about to be overwritten —
        // they apply to the origin's relationship with the public internet,
        // not to your cache layer.
        const response = await fetch(upstream, {
            method: request.method,
            headers: request.headers,
            body: request.body,
        });

// Replace the origin's Cache-Control with your own. This is the
        // whole point of the pattern: you decide how long Workers Caching
        // stores this response, regardless of what the origin says.
        const headers = new Headers(response.headers);
        headers.set("Cache-Control", "public, max-age=300");
        headers.set("Cache-Tag", "origin:example");

return new Response(response.body, {
            status: response.status,
            statusText: response.statusText,
            headers,
        });
    }
}

// Default entrypoint. Forwards every request through the cached entrypoint.
export default {
    async fetch(request, env, ctx): Promise&lt;Response> {
        // The gateway's cache is disabled (see the Wrangler configuration
        // above), so it runs on every request and forwards to the cached
        // CachedOrigin entrypoint.
        return ctx.exports.CachedOrigin.fetch(request);
    },
} satisfies ExportedHandler;

What is happening here:

  • The cache layer is yours. The origin's Cache-Control is replaced before the response reaches Workers Caching, so the TTL, freshness directives, and Cache-Tag namespace are all controlled by your code. You decide when the cache holds onto a response, and you decide when to purge it via ctx.cache.purge().
  • The origin's own caching model is untouched. Your Worker is the only thing that sees the rewritten Cache-Control. The origin still serves its other clients with whatever caching contract it published — you have not changed its behaviour or its security model, you have only added a layer in front of it for your application.
  • Cache hits never touch the origin. Workers Caching sits in front of CachedOrigin, so a hit returns the stored response without invoking fetch against the upstream. This is what cuts the origin request volume and the latency of every cached call.

A few common extensions to this pattern:

  • Per-resource TTLs. If different paths on the upstream should have different freshness, branch on url.pathname inside CachedOrigin and set a different max-age (and a different Cache-Tag) for each. The cache key already includes the path and query string, so each resource gets its own entry.
  • Per-user caching. If your application authenticates the caller and the upstream returns user-specific data, authenticate in the outer entrypoint and pass the user identifier via ctx.props to CachedOrigin — the same shape as Per-user authenticated responses. Each user gets their own cache entry, and one user can never receive another user's cached response.
  • Stale-while-revalidate. If the origin is slow or flaky, set Cache-Control: public, max-age=60, stale-while-revalidate=600 on the cached response. Most requests return the cached body immediately, and Workers Caching refreshes the origin in the background. Refer to Use stale-while-revalidate for low-latency refreshes.
  • Targeted invalidation. Tag responses with Cache-Tag values that reflect your application's data model (for example, Cache-Tag: origin:example, product:42). When you know the upstream has changed — a webhook fires, an admin action runs — call ctx.cache.purge({ tags: ["product:42"] }) and the next request repopulates the cache.

This is the same building block as every other example on this page. The only difference is that the "expensive work" the cached entrypoint does on a miss is a fetch to somebody else's server. The control over how long that response lives, how it is keyed, and when it is invalidated stays entirely in your Worker.

Composing the patterns

All four examples are the same architecture seen through four lenses:

Outer entrypoint What the cache stage is doing Inner entrypoint
Authenticate the request Caching an expensive computation per user Loads or computes the user's data
Restore Accept-Encoding Caching one variant per real encoding Loads the correctly-encoded asset
Route reads vs. writes Caching reads, invalidating them on writes Wraps a Durable Object behind a Cache-Tag
Forward the request as-is Caching a third-party origin under your terms Fetches the upstream and overlays Cache-Control

The only thing that changes between rows is what the outer entrypoint does before the call and what the inner entrypoint does on a miss. The cache stage in the middle is the same primitive every time — keyed by the inner entrypoint, the request path and query string, and ctx.props; configured by the inner entrypoint's Cache-Control and Cache-Tag; purged by ctx.cache.purge() or invalidated by ctx.cache.invalidate() from whichever entrypoint owns the data.

That uniformity is what makes the patterns compose. Nothing stops you from stacking them in a single Worker:

  • An outer entrypoint that authenticates and routes.
  • A normalization entrypoint that strips tracking query parameters, restores Accept-Encoding, and shapes the request into a canonical form.
  • A cached entrypoint that fronts a Durable Object, tagged for purging.
  • A separate cached entrypoint for an unauthenticated public endpoint, also reachable through the same outer entrypoint, with its own cache key and Cache-Tag namespace.

Each call between these entrypoints goes through its own cache stage. The chain is built out of the same three building blocks — WorkerEntrypoint, ctx.exports, and a Cache-Control header — and the cache is a stage of the chain rather than a separate system bolted on. Whatever you would have configured in a cache rules engine, you now write as code: which entrypoint runs, what request gets forwarded, what props get passed, what Cache-Control gets returned, what gets purged.

There is no fixed list of patterns. Workers Caching gives you a cache between every Worker entrypoint — what you build with that is up to you.

Was this helpful?

YesNo

On this page

Examples (13)·续2 - 图1Docs

{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/workers/cache/examples/#page","headline":"Examples","description":"Patterns for combining Workers Caching with authentication, request normalization, and Durable Objects.","url":"https://developers.cloudflare.com/workers/cache/examples/","inLanguage":"en","image":"https://developers.cloudflare.com/workers/cache/examples/og.png?v=e7b4e199277c303d","dateModified":"2026-10-12","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}


description: Current limitations, unsupported scenarios, and how Workers Caching relates to other Cloudflare caches. title: Limitations image: https://developers.cloudflare.com/workers/cache/limitations/og.png?v=d53a494830f53dd3


Skip to content

Documentation Index
Fetch the complete documentation index at: https://developers.cloudflare.com/workers/llms.txt
Use this file to discover all available pages before exploring further.