Display an explanation when a user visits a selected page. The Worker returns a synthetic response without calling next(), so Gateway does not send the request to the original destination.
This template displays a notice with a link to an approved alternative. It does not implement a temporary exception, approval workflow, or an acknowledgement that unlocks the original site.
In your Worker project, install @cloudflare/zt-types ↗︎ version 2026.10.1 or later as a development dependency:
npm i -D @cloudflare/zt-typesyarn add -D @cloudflare/zt-typespnpm add -D @cloudflare/zt-typesbun add -d @cloudflare/zt-typesKeep the import type declarations for GatewayMiddlewareContext and GatewayMiddlewareNext in src/index.ts. They describe the Gateway context and continuation and are removed when TypeScript compiles. Import the runtime WorkerEntrypoint class from cloudflare:workers.
Use this script as src/index.ts in the Worker project. Replace the approved application URL and explanation with your organization's guidance. It needs no bindings.
import { WorkerEntrypoint } from "cloudflare:workers";
export class GatewayMiddleware extends WorkerEntrypoint {
async handle(request, context, next) {
if (
request.method !== "GET" ||
!request.headers.get("accept")?.includes("text/html")
) {
return next(request);
}
return new Response(
`<!doctype html>
<html lang="en"><meta charset="utf-8">
<title>Use the approved application</title>
<h1>Keep work data in the approved application</h1>
<p>Use your company workspace when sharing work files.</p>
<p><a href="https://approved.example.com/">Open your company workspace</a></p>
</html>`,
{
status: 200,
headers: {
"content-type": "text/html; charset=utf-8",
"cache-control": "no-store",
"content-security-policy":
"default-src 'none'; base-uri 'none'; frame-ancestors 'none'",
"x-content-type-options": "nosniff",
},
},
);
}
}import { WorkerEntrypoint } from "cloudflare:workers";
import type {
GatewayMiddlewareContext,
GatewayMiddlewareNext,
} from "@cloudflare/zt-types";
export class GatewayMiddleware extends WorkerEntrypoint {
async handle(
request: Request,
context: GatewayMiddlewareContext,
next: GatewayMiddlewareNext,
): Promise<Response> {
if (
request.method !== "GET" ||
!request.headers.get("accept")?.includes("text/html")
) {
return next(request);
}
return new Response(
`<!doctype html>
<html lang="en"><meta charset="utf-8">
<title>Use the approved application</title>
<h1>Keep work data in the approved application</h1>
<p>Use your company workspace when sharing work files.</p>
<p><a href="https://approved.example.com/">Open your company workspace</a></p>
</html>`,
{
status: 200,
headers: {
"content-type": "text/html; charset=utf-8",
"cache-control": "no-store",
"content-security-policy":
"default-src 'none'; base-uri 'none'; frame-ancestors 'none'",
"x-content-type-options": "nosniff",
},
},
);
}
}Deploy the Worker and create a Forward policy for one test host, a selected page path, HTTP Method GET, and your test user group. Keep the approved alternative outside this policy's scope.
Visit the selected page in a browser and confirm that the coaching page appears. Check the test origin's logs to confirm that no upstream request was made for this visit. A request without an HTML Accept header passes through normally.
The method and Accept checks select when to show a page. They are not access controls because clients can change request headers.
Use Gateway Block policies when you need to prevent access. An HTTP request policy with a Block action prevents Worker invocation, while a Block decision from body policies or inspection can interrupt the Worker.
Synthetic responses still undergo Gateway response inspection.