Cloudflare Workers Snippets
Browse ready-to-deploy Cloudflare Workers patterns: routing, CORS, A/B testing, redirects, rate limiting, caching, and header manipulation. Click to copy.
Routing & Responses
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
const path = url.pathname;
if (path === '/' || path === '/home') {
return new Response('Welcome to the homepage', {
headers: { 'Content-Type': 'text/plain' },
});
}
if (path.startsWith('/api/')) {
return new Response('API endpoint hit: ' + path, {
headers: { 'Content-Type': 'text/plain' },
});
}
if (path === '/about') {
return new Response('About page', {
headers: { 'Content-Type': 'text/plain' },
});
}
// Unmatched - pass through to origin
return fetch(request);
},
}; export default {
async fetch(request, env, ctx) {
const data = {
success: true,
message: 'Hello from the edge',
timestamp: Date.now(),
region: request.cf?.colo ?? 'unknown',
};
return new Response(JSON.stringify(data, null, 2), {
status: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Cache-Control': 'no-store',
},
});
},
}; const HTML = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Edge Page</title>
</head>
<body>
<h1>Hello from Cloudflare Workers</h1>
<p>Served at the edge.</p>
</body>
</html>`;
export default {
async fetch(request, env, ctx) {
return new Response(HTML, {
headers: { 'Content-Type': 'text/html;charset=UTF-8' },
});
},
}; const KNOWN_ROUTES = new Set(['/', '/about', '/contact', '/blog']);
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
if (!KNOWN_ROUTES.has(url.pathname)) {
const body = JSON.stringify({
error: 'Not Found',
path: url.pathname,
status: 404,
});
return new Response(body, {
status: 404,
headers: { 'Content-Type': 'application/json' },
});
}
// Pass known routes to origin
return fetch(request);
},
}; Headers & Security
export default {
async fetch(request, env, ctx) {
const response = await fetch(request);
// Clone so we can mutate headers
const newResponse = new Response(response.body, response);
const h = newResponse.headers;
h.set('X-Frame-Options', 'SAMEORIGIN');
h.set('X-Content-Type-Options', 'nosniff');
h.set('Referrer-Policy', 'strict-origin-when-cross-origin');
h.set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
h.set(
'Content-Security-Policy',
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';"
);
h.set(
'Strict-Transport-Security',
'max-age=63072000; includeSubDomains; preload'
);
return newResponse;
},
}; const ALLOWED_ORIGIN = 'https://yourdomain.com';
const CORS_HEADERS = {
'Access-Control-Allow-Origin': ALLOWED_ORIGIN,
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Max-Age': '86400',
};
export default {
async fetch(request, env, ctx) {
// Handle preflight
if (request.method === 'OPTIONS') {
return new Response(null, { status: 204, headers: CORS_HEADERS });
}
const response = await fetch(request);
const newResponse = new Response(response.body, response);
for (const [key, value] of Object.entries(CORS_HEADERS)) {
newResponse.headers.set(key, value);
}
return newResponse;
},
}; const SENSITIVE_HEADERS = [
'X-Powered-By',
'Server',
'X-AspNet-Version',
'X-AspNetMvc-Version',
];
export default {
async fetch(request, env, ctx) {
const response = await fetch(request);
const newResponse = new Response(response.body, response);
for (const header of SENSITIVE_HEADERS) {
newResponse.headers.delete(header);
}
return newResponse;
},
}; // Note: in-memory counters reset per isolate restart.
// For persistent rate limiting across all edge nodes, use Workers KV or Durable Objects.
const LIMIT = 60; // max requests
const WINDOW_MS = 60000; // per 60 seconds
const counters = new Map();
export default {
async fetch(request, env, ctx) {
const ip = request.headers.get('CF-Connecting-IP') ?? 'unknown';
const now = Date.now();
const entry = counters.get(ip);
if (!entry || now - entry.start > WINDOW_MS) {
counters.set(ip, { count: 1, start: now });
} else {
entry.count++;
if (entry.count > LIMIT) {
return new Response('Too Many Requests', {
status: 429,
headers: {
'Retry-After': '60',
'Content-Type': 'text/plain',
},
});
}
}
return fetch(request);
},
}; Redirects
// Map of old path -> new full URL (or path)
const REDIRECTS = new Map([
['/old-page', 'https://example.com/new-page'],
['/blog/2020/post-title', 'https://example.com/posts/post-title'],
['/products', 'https://example.com/shop'],
['/contact-us', 'https://example.com/contact'],
]);
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
const destination = REDIRECTS.get(url.pathname);
if (destination) {
return Response.redirect(destination, 301);
}
return fetch(request);
},
}; // Set to true to ADD trailing slash, false to REMOVE it
const ADD_SLASH = false;
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
const path = url.pathname;
// Skip root, files with extensions, and already-correct paths
if (path === '/') return fetch(request);
const hasExt = path.includes('.');
if (hasExt) return fetch(request);
if (!ADD_SLASH && path.endsWith('/')) {
url.pathname = path.slice(0, -1);
return Response.redirect(url.toString(), 301);
}
if (ADD_SLASH && !path.endsWith('/')) {
url.pathname = path + '/';
return Response.redirect(url.toString(), 301);
}
return fetch(request);
},
}; export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
// Redirect www.example.com -> example.com
if (url.hostname.startsWith('www.')) {
url.hostname = url.hostname.slice(4);
return Response.redirect(url.toString(), 301);
}
return fetch(request);
},
}; Caching
export default {
async fetch(request, env, ctx) {
// Only cache GET requests
if (request.method !== 'GET') return fetch(request);
const cacheKey = new Request(request.url, request);
const cache = caches.default;
// Try cache first
let response = await cache.match(cacheKey);
if (response) {
// Add a header so you can verify cache hit in DevTools
const hit = new Response(response.body, response);
hit.headers.set('X-Cache', 'HIT');
return hit;
}
// Cache miss - fetch from origin
response = await fetch(request);
// Only cache successful responses
if (response.ok) {
const toCache = new Response(response.body, response);
toCache.headers.set('Cache-Control', 'public, max-age=3600');
ctx.waitUntil(cache.put(cacheKey, toCache.clone()));
toCache.headers.set('X-Cache', 'MISS');
return toCache;
}
return response;
},
}; // Override TTL for different content types
const TTL_MAP = {
'text/html': 300, // 5 minutes
'application/json': 60, // 1 minute
'text/css': 86400, // 1 day
'application/javascript': 86400,
'image/': 2592000, // 30 days (prefix match)
};
export default {
async fetch(request, env, ctx) {
const response = await fetch(request);
if (!response.ok) return response;
const contentType = response.headers.get('Content-Type') ?? '';
let ttl = 0;
for (const [type, seconds] of Object.entries(TTL_MAP)) {
if (contentType.startsWith(type)) {
ttl = seconds;
break;
}
}
const newResponse = new Response(response.body, response);
if (ttl > 0) {
newResponse.headers.set('Cache-Control', 'public, max-age=' + ttl);
}
return newResponse;
},
}; // Cookie name prefixes that indicate a logged-in user
const AUTH_COOKIE_PREFIXES = [
'wordpress_logged_in',
'wp-settings',
'woocommerce_cart_hash',
'PHPSESSID',
'laravel_session',
];
function isLoggedIn(cookieHeader) {
if (!cookieHeader) return false;
return AUTH_COOKIE_PREFIXES.some((prefix) =>
cookieHeader.includes(prefix)
);
}
export default {
async fetch(request, env, ctx) {
const cookies = request.headers.get('Cookie') ?? '';
if (isLoggedIn(cookies)) {
// Bypass Cloudflare cache for authenticated users
const bypassRequest = new Request(request, {
headers: new Headers(request.headers),
});
bypassRequest.headers.set('Cache-Control', 'no-cache');
return fetch(bypassRequest);
}
return fetch(request);
},
}; A/B Testing
const COOKIE_NAME = 'ab_variant';
const VARIANT_A_URL = 'https://example.com/';
const VARIANT_B_URL = 'https://example.com/variant-b/';
function getVariantFromCookie(cookieHeader) {
if (!cookieHeader) return null;
const match = cookieHeader.match(new RegExp(COOKIE_NAME + '=([^;]+)'));
return match ? match[1] : null;
}
export default {
async fetch(request, env, ctx) {
const cookies = request.headers.get('Cookie') ?? '';
let variant = getVariantFromCookie(cookies);
let isNew = false;
if (!variant) {
variant = Math.random() < 0.5 ? 'a' : 'b';
isNew = true;
}
const targetUrl = variant === 'a' ? VARIANT_A_URL : VARIANT_B_URL;
const originRequest = new Request(targetUrl, request);
const response = await fetch(originRequest);
const newResponse = new Response(response.body, response);
if (isNew) {
// Sticky for 7 days
newResponse.headers.set(
'Set-Cookie',
COOKIE_NAME + '=' + variant + '; Max-Age=604800; Path=/; SameSite=Lax'
);
}
return newResponse;
},
}; const VARIANTS = {
control: 'https://example.com/landing-a/',
treatment: 'https://example.com/landing-b/',
};
export default {
async fetch(request, env, ctx) {
const cookieHeader = request.headers.get('Cookie') ?? '';
const match = cookieHeader.match(/experiment=([^;]+)/);
const variant = match ? match[1] : null;
// If the variant cookie exists and maps to a known URL, serve it
if (variant && VARIANTS[variant]) {
return fetch(new Request(VARIANTS[variant], request));
}
// No cookie - serve control and do not set a new cookie
// (let your experiment platform assign variants separately)
return fetch(new Request(VARIANTS.control, request));
},
}; Utilities
// Map of ISO 3166-1 alpha-2 country codes to redirect URLs
const GEO_REDIRECTS = new Map([
['GB', 'https://uk.example.com/'],
['DE', 'https://de.example.com/'],
['FR', 'https://fr.example.com/'],
['AU', 'https://au.example.com/'],
]);
export default {
async fetch(request, env, ctx) {
const country = request.cf?.country ?? null;
if (country && GEO_REDIRECTS.has(country)) {
const url = new URL(request.url);
const destination = new URL(GEO_REDIRECTS.get(country));
// Preserve the path and query string
destination.pathname = url.pathname;
destination.search = url.search;
return Response.redirect(destination.toString(), 302);
}
return fetch(request);
},
}; const BOT_PATTERN = /bot|crawl|slurp|spider|mediapartners|google|bingbot|facebookexternalhit/i;
const MOBILE_PATTERN = /mobile|android|iphone|ipod|blackberry|windows phone/i;
function classify(ua) {
if (!ua) return 'unknown';
if (BOT_PATTERN.test(ua)) return 'bot';
if (MOBILE_PATTERN.test(ua)) return 'mobile';
return 'desktop';
}
export default {
async fetch(request, env, ctx) {
const ua = request.headers.get('User-Agent') ?? '';
const deviceType = classify(ua);
// Example: serve bots a simplified page
if (deviceType === 'bot') {
const url = new URL(request.url);
url.searchParams.set('_lite', '1');
return fetch(new Request(url.toString(), request));
}
// Add device hint header for the origin to use
const newRequest = new Request(request);
newRequest.headers.set('X-Device-Type', deviceType);
return fetch(newRequest);
},
}; export default {
async fetch(request, env, ctx) {
const startTime = Date.now();
const url = new URL(request.url);
// Log request metadata before forwarding
const reqLog = {
method: request.method,
path: url.pathname + url.search,
country: request.cf?.country ?? '-',
colo: request.cf?.colo ?? '-',
ip: request.headers.get('CF-Connecting-IP') ?? '-',
ua: (request.headers.get('User-Agent') ?? '-').slice(0, 80),
};
const response = await fetch(request);
const durationMs = Date.now() - startTime;
// Logs are visible in Cloudflare dashboard under Workers - Logs
console.log(JSON.stringify({
...reqLog,
status: response.status,
durationMs,
ts: new Date().toISOString(),
}));
return response;
},
}; About this tool
Cloudflare Workers is a serverless execution environment that runs JavaScript (and other languages via WebAssembly) at Cloudflare's global edge network - currently spanning over 300 cities worldwide. When a request hits a Worker, it executes within milliseconds of the visitor because it runs in the same data centre that serves their traffic, not in a remote origin server. This edge computing model means typical Worker response times are in the single-digit milliseconds range, often 10-50x faster than a traditional server-side request that has to travel across the internet.
Workers use the modern ES module syntax shown in all snippets above: export default { async fetch(request, env, ctx) {...} }. The request object is a standard Fetch API Request, env holds bindings to KV stores, Durable Objects, R2 buckets, and secrets you configure in the dashboard, and ctx provides waitUntil() for deferred work like cache writes after the response is sent. This API surface is deliberately minimal and runs in the same V8 isolate model used by Chrome - many Node.js built-ins are not available, but the Web Platform APIs (Fetch, Cache, URL, Headers, TextEncoder, crypto) all are.
When should you choose Workers over Cloudflare Page Rules or Transform Rules? Page Rules and Transform Rules are point-and-click configuration tools suited for simple redirects, cache settings, and header tweaks on a per-URL basis. Workers are the right choice when you need conditional logic, multiple steps, external API calls, or anything that cannot be expressed as a single rule - A/B testing, geo-routing with path rewriting, dynamic JSON responses, rate limiting, or request authentication. For bulk static redirects (thousands of paths), Cloudflare's native Bulk Redirects feature is often simpler than a Worker because it is evaluated before the Worker runs.
On the free plan, Workers receive 100,000 requests per day (across all your Workers) with a CPU time limit of 10ms per invocation. Paid plans start at $5/month for 10 million requests and 30ms CPU time per request. The CPU time limit counts only active execution time - waiting for a fetch() to return does not count. For most use cases like header manipulation, redirects, and lightweight routing, the 10ms limit is more than enough. CPU-intensive work like image processing or complex cryptography typically requires the paid tier or moving that work to an API the Worker calls.
All snippets in this library use the modern ES module format and are ready to paste directly into the Cloudflare Workers dashboard editor or into a src/index.js file in a Wrangler project. Replace the placeholder URLs, cookie names, and constants with your own values, then test using Cloudflare's built-in quick edit preview or wrangler dev locally before deploying.