Purge and invalidate the cache·续

When you want to purge everything a specific version wrote — for example, after a rollback — purge the version tag:

src/admin.jsjs

export default {
    async fetch(request, env, ctx) {
        const versionId = new URL(request.url).searchParams.get("version");
        if (!versionId) return new Response("Missing version", { status: 400 });

await ctx.cache.purge({ tags: [`v:${versionId}`] });

return new Response("Purged", { status: 200 });
    },
};

src/admin.tsts

export default {
    async fetch(request, env, ctx): Promise<Response> {
        const versionId = new URL(request.url).searchParams.get("version");
        if (!versionId) return new Response("Missing version", { status: 400 });

await ctx.cache.purge({ tags: [`v:${versionId}`] });

return new Response("Purged", { status: 200 });
    },
} satisfies ExportedHandler;

Purge by path prefix

pathPrefixes purges every cached response whose request path begins with one of the given prefixes:

src/index.jsjs

export default {
    async fetch(request, env, ctx) {
        // Purge everything under /blog/2025/ for the current entrypoint.
        await ctx.cache.purge({
            pathPrefixes: ["/blog/2025/"],
        });

return new Response("Purged", { status: 200 });
    },
};

src/index.tsts

export default {
    async fetch(request, env, ctx): Promise<Response> {
        // Purge everything under /blog/2025/ for the current entrypoint.
        await ctx.cache.purge({
            pathPrefixes: ["/blog/2025/"],
        });

return new Response("Purged", { status: 200 });
    },
} satisfies ExportedHandler;

Entries in pathPrefixes are paths, not full URLs. A prefix must not include a scheme, host, query string, or fragment — passing something like https://example.com/blog/ is invalid input, not a prefix that simply fails to match. A leading slash is optional (/images and images are treated the same) but recommended for clarity.

pathPrefixes is scoped to the entrypoint that makes the purge call. A purge({ pathPrefixes: ["/blog/"] }) from PublicAPI will not affect cached responses stored by AdminAPI, even when their paths also start with /blog/.

Purge a single URL

There is no dedicated "purge by URL" mode. To purge a single cached URL, pass its path as a single-element pathPrefixes array:

src/index.jsjs

export default {
    async fetch(request, env, ctx) {
        // Purge the cached response for exactly /blog/2026/hello-world.
        await ctx.cache.purge({
            pathPrefixes: ["/blog/2026/hello-world"],
        });

return new Response("Purged", { status: 200 });
    },
};

src/index.tsts

export default {
    async fetch(request, env, ctx): Promise<Response> {
        // Purge the cached response for exactly /blog/2026/hello-world.
        await ctx.cache.purge({
            pathPrefixes: ["/blog/2026/hello-world"],
        });

return new Response("Purged", { status: 200 });
    },
} satisfies ExportedHandler;

Because pathPrefixes matches on the start of the request path, passing the full path matches only that path — plus any paths that happen to extend it (for example, /blog/2026/hello-world-2). If you need exact-match semantics with no risk of over-purge, use a tag instead.

Purge everything

Delete every cached response stored by the calling entrypoint:

src/index.jsjs

export default {
    async fetch(request, env, ctx) {
        await ctx.cache.purge({ purgeEverything: true });

return new Response("Purged", { status: 200 });
    },
};

src/index.tsts

export default {
    async fetch(request, env, ctx): Promise<Response> {
        await ctx.cache.purge({ purgeEverything: true });

return new Response("Purged", { status: 200 });
    },
} satisfies ExportedHandler;

Use this sparingly. Purging everything causes all subsequent requests to miss the cache until they can be re-filled, which temporarily increases load on your Worker and any upstream services it calls.

Invalidate cached responses

invalidate() is the soft counterpart of purge(). It accepts the same options (tags, pathPrefixes, or purgeEverything), selects cached responses the same way, follows the same per-entrypoint scoping, and resolves to the same result object. You can call it as ctx.cache.invalidate(...) or as cache.invalidate(...) imported from cloudflare:workers — the same two forms as purge().

The two methods differ in what happens to the matching cached responses:

Method Matching cached responses Next matching request
purge() Deleted Cache miss. Your Worker runs and produces a fresh response.
invalidate() Kept, but marked stale Revalidated. The cache sends your Worker a conditional request instead.

To revalidate an invalidated response, the cache calls your Worker with conditional request headers built from the validators stored with the response — for example, If-None-Match carrying the cached ETag, or If-Modified-Since carrying the cached Last-Modified. If your Worker answers 304 Not Modified, the cache keeps the stored body and serves it with Cf-Cache-Status: REVALIDATED, and later requests are HITs again. If your Worker answers with a full 200 response instead, that response replaces the cached one.

invalidate() therefore saves work only when your Worker emits validators (ETag or Last-Modified) and answers matching conditional requests with 304. This helps most when one call covers many responses but only some of them changed. Each unchanged response then costs a validator check instead of a full regeneration. If your Worker does not emit validators, or always answers with a full response, every revalidation regenerates the response and invalidate() saves no work compared to purge().

In this example, a catalog sync writes every product and then invalidates all product pages. Each product carries a revision that changes only when its data changes, and the Worker uses that revision as the ETag. Pages for unchanged products revalidate with 304, so the Worker renders only the pages that changed:

src/index.jsjs

function renderProductPage(product) {
    // Stands in for the expensive rendering work that a 304 response skips.
    return `<!doctype html><title>${product.name}</title>`;
}

export default {
    async fetch(request, env, ctx) {
        const url = new URL(request.url);

if (request.method === "POST" && url.pathname === "/sync") {
            // Write every product from the upstream catalog. Many are unchanged.
            const products = await request.json();
            await Promise.all(
                products.map((product) =>
                    env.PRODUCTS.put(product.id, JSON.stringify(product)),
                ),
            );

// Mark every cached product page stale instead of deleting it.
            await ctx.cache.invalidate({ tags: ["products"] });

return new Response("Synced", { status: 200 });
        }

const id = url.pathname.split("/").pop() ?? "";
        const product = await env.PRODUCTS.get(id, "json");
        if (!product) return new Response("Not found", { status: 404 });

const etag = `"${product.revision}"`;
        const headers = {
            "Cache-Control": "public, max-age=86400",
            "Cache-Tag": `products,product-${id}`,
            ETag: etag,
        };

// To revalidate an invalidated response, the cache sends the stored ETag
        // in If-None-Match. If the product has not changed, answer 304 and skip
        // rendering. The cache keeps the body it already has.
        if (request.headers.get("If-None-Match") === etag) {
            return new Response(null, { status: 304, headers });
        }

return new Response(renderProductPage(product), {
            headers: { ...headers, "Content-Type": "text/html" },
        });
    },
};

src/index.tsts

interface Env {
    PRODUCTS: KVNamespace;
}

interface Product {
    id: string;
    name: string;
    // Changes only when the product data changes.
    revision: string;
}

function renderProductPage(product: Product): string {
    // Stands in for the expensive rendering work that a 304 response skips.
    return `<!doctype html><title>${product.name}</title>`;
}

export default {
    async fetch(request, env, ctx): Promise<Response> {
        const url = new URL(request.url);

if (request.method === "POST" && url.pathname === "/sync") {
            // Write every product from the upstream catalog. Many are unchanged.
            const products = await request.json<Product[]>();
            await Promise.all(
                products.map((product) =>
                    env.PRODUCTS.put(product.id, JSON.stringify(product)),
                ),
            );

// Mark every cached product page stale instead of deleting it.
            await ctx.cache.invalidate({ tags: ["products"] });

return new Response("Synced", { status: 200 });
        }

const id = url.pathname.split("/").pop() ?? "";
        const product = await env.PRODUCTS.get<Product>(id, "json");
        if (!product) return new Response("Not found", { status: 404 });

const etag = `"${product.revision}"`;
        const headers = {
            "Cache-Control": "public, max-age=86400",
            "Cache-Tag": `products,product-${id}`,
            ETag: etag,
        };

// To revalidate an invalidated response, the cache sends the stored ETag
        // in If-None-Match. If the product has not changed, answer 304 and skip
        // rendering. The cache keeps the body it already has.
        if (request.headers.get("If-None-Match") === etag) {
            return new Response(null, { status: 304, headers });
        }

return new Response(renderProductPage(product), {
            headers: { ...headers, "Content-Type": "text/html" },
        });
    },
} satisfies ExportedHandler<Env>;

Purge propagation

Purges triggered by ctx.cache.purge() use Cloudflare's Instant Purge infrastructure and propagate globally with the same guarantees as zone-level purges.

Return value

purge() and invalidate() resolve to the same result object. Check success to confirm the request was accepted, and inspect errors if it was not:

src/index.jsjs

export default {
    async fetch(request, env, ctx) {
        const result = await ctx.cache.purge({ tags: ["blog-posts"] });

if (!result.success) {
            console.error("Cache purge failed", result.errors);
            return new Response("Purge failed", { status: 500 });
        }

return new Response("Purged", { status: 200 });
    },
};

src/index.tsts

export default {
    async fetch(request, env, ctx): Promise<Response> {
        const result = await ctx.cache.purge({ tags: ["blog-posts"] });

if (!result.success) {
            console.error("Cache purge failed", result.errors);
            return new Response("Purge failed", { status: 500 });
        }

return new Response("Purged", { status: 200 });
    },
} satisfies ExportedHandler;

On failure, each error in errors carries a numeric code and a human-readable message you can log or surface to the caller.

Rate limits

purge() uses the same rate-limiting system as Cloudflare's zone purge API. However, because Workers Caching is associated with the Worker rather than the zone, Workers Cache always uses the Free tier limits described in Availability and limits, regardless of your account or zone plan. When the purge is rate-limited, success is false and errors contains an entry describing the rejection.

Was this helpful?

YesNo

On this page

Purge and invalidate the cache·续 - 图1Docs

{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/workers/cache/purge/#page","headline":"Purge and invalidate the cache","description":"Delete cached responses with ctx.cache.purge() or mark them stale with ctx.cache.invalidate() — by tag, by path prefix, or everything.","url":"https://developers.cloudflare.com/workers/cache/purge/","inLanguage":"en","image":"https://developers.cloudflare.com/workers/cache/purge/og.png?v=232653489c14e903","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: Set up continuous integration and continuous deployment for your Workers. title: CI/CD image: https://developers.cloudflare.com/workers/ci-cd/og.png?v=66d3d2c68cdaeca5


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.