This guide helps you determine when to use Snippets or Workers on Cloudflare's global network. It provides best practices, comparisons, and real-world use cases to help you choose the right product for your workload.
Cloudflare Snippets provide a fast, declarative way to modify HTTP requests and responses at the edge â without requiring a full-stack compute platform. Snippets extend Cloudflare Rules by allowing you to write JavaScript-based logic that modifies requests before they reach an origin and responses after they return from upstream.
Snippets enable you to:
Snippets are included at no additional cost in all paid plans, making them the preferred solution for lightweight edge logic.
By contrast, Cloudflare Workers provide a full-stack compute platform designed for applications requiring state, compute, and integrations with Cloudflareâs Developer Platform. Workers operate on a usage-based pricing model and include a free tier.
Choosing the right productSnippets are ideal for fast, cost-free request and response modifications at the edge. They extend Cloudflare Rules without requiring additional infrastructure or external solutions.
Below are practical use cases demonstrating Snippets in action. You can find more templates to get started in the Examples section.
Modifies request and response headers dynamically.
export default {
async fetch(request) {
// Get the current timestamp
const timestamp = Date.now();
// Convert the timestamp to hexadecimal format
const hexTimestamp = timestamp.toString(16);
// Clone the request and add the custom header with HEX timestamp
const modifiedRequest = new Request(request, {
headers: new Headers(request.headers),
});
modifiedRequest.headers.set("X-Hex-Timestamp", hexTimestamp);
// Pass the modified request to the origin
const response = await fetch(modifiedRequest);
// Clone the response so that it's no longer immutable
const newResponse = new Response(response.body, response);
// Add a custom header with a value to the response
newResponse.headers.append(
"x-snippets-hello",
"Hello from Cloudflare Snippets",
);
// Delete headers from the response
newResponse.headers.delete("x-header-to-delete");
newResponse.headers.delete("x-header2-to-delete");
// Adjust the value for an existing header in the response
newResponse.headers.set("x-header-to-change", "NewValue");
// Serve modified response to the visitor
return newResponse;
},
};
Serve a custom maintenance page
Routes traffic to a maintenance page when your origin is undergoing a planned maintenance.
export default {
async fetch(request) {
return new Response(
`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>We'll Be Right Back!</title>
<style> body { font-family: Arial, sans-serif; text-align: center; padding: 20px; } </style>
</head>
<body>
<h1>We'll Be Right Back!</h1>
<p>Our site is undergoing maintenance. Check back soon!</p>
</body>
</html>
`,
{ status: 503, headers: { "Content-Type": "text/html" } },
);
},
};
Performs programmatic caching at the edge to reduce origin load.
const CACHE_DURATION = 30 * 24 * 60 * 60; // 30 days
export default {
async fetch(request) {
const cache = caches.default;
const cacheKey = new Request(request.url, { method: "GET" });
let response = await cache.match(cacheKey);
if (!response) {
response = await fetch(request);
response = new Response(response.body, response);
response.headers.set("Cache-Control", `s-maxage=${CACHE_DURATION}`);
await cache.put(cacheKey, response.clone());
}
return response;
},
};
Redirect based on country code
Redirects visitors based on their geographic location.
export default {
async fetch(request) {
const country = request.cf.country;
const redirectMap = {
US: "https://example.com/us",
EU: "https://example.com/eu",
};
if (redirectMap[country])
return Response.redirect(redirectMap[country], 301);
return fetch(request);
},
};
Redirect 403 Forbidden to a different page
If the origin responded with 403 Forbidden
error code, redirects visitor to a different page.
export default {
async fetch(request) {
// Send original request to the origin
const response = await fetch(request);
// Check if origin responded with 403 status code
if (response.status == 403) {
// If so, redirect to this URL
const destinationURL = "https://example.com";
// With this status code
const statusCode = 301;
// Serve redirect
return Response.redirect(destinationURL, statusCode);
}
// Otherwise, serve origin's response
else {
return response;
}
},
};
If the response to the original request is not 200 OK
or a redirect, sends to another origin.
export default {
async fetch(request) {
// Send original request to the origin
const response = await fetch(request);
// If response is not 200 OK or a redirect, send to another origin
if (!response.ok && !response.redirected) {
// First, clone the original request to construct a new request
const newRequest = new Request(request);
// Add a header to identify a re-routed request at the new origin
newRequest.headers.set("X-Rerouted", "1");
// Clone the original URL
const url = new URL(request.url);
// Send request to a different origin / hostname
url.hostname = "example.com";
// Serve response to the new request from the origin
return await fetch(url, newRequest);
}
// If response is 200 OK or a redirect, serve it
return response;
},
};
Remove fields from API response
If the origin responds with JSON, deletes sensitive fields before returning a response to the visitor.
export default {
async fetch(request) {
// Send original request to the origin
const response = await fetch(request);
// Check if origin responded with JSON
try {
// Parse API response as JSON
var api_response = response.json();
// Specify the fields you want to delete. For example, to delete "botManagement" array from parsed JSON:
delete api_response.botManagement;
// Serve modified API response
return Response.json(api_response);
} catch (err) {
// On failure, serve unmodified origin's response
return response;
}
},
};
Adjusts Cross-Origin Resource Sharing (CORS) â headers and handles preflight requests.
// Define CORS headers
const corsHeaders = {
"Access-Control-Allow-Origin": "*", // Replace * with your allowed origin(s)
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS", // Adjust allowed methods as needed
"Access-Control-Allow-Headers": "Content-Type, Authorization", // Adjust allowed headers as needed
"Access-Control-Max-Age": "86400", // Adjust max age (in seconds) as needed
};
export default {
async fetch(request) {
// Make a copy of the request to modify its headers
const modifiedRequest = new Request(request);
// Handle preflight requests (OPTIONS)
if (request.method === "OPTIONS") {
return new Response(null, {
headers: {
...corsHeaders,
},
status: 200, // Respond with OK status for preflight requests
});
}
// Pass the modified request through to the origin
const response = await fetch(modifiedRequest);
// Make a copy of the response to modify its headers
const modifiedResponse = new Response(response.body, response);
// Set CORS headers on the response
Object.keys(corsHeaders).forEach((header) => {
modifiedResponse.headers.set(header, corsHeaders[header]);
});
return modifiedResponse;
},
};
Rewrite links on HTML pages
Replaces outdated links without having to make changes on your origin.
export default {
async fetch(request) {
// Define the old hostname here.
const OLD_URL = "oldsite.com";
// Then add your new hostname that should replace the old one.
const NEW_URL = "newsite.com";
class AttributeRewriter {
constructor(attributeName) {
this.attributeName = attributeName;
}
element(element) {
const attribute = element.getAttribute(this.attributeName);
if (attribute) {
element.setAttribute(
this.attributeName,
attribute.replace(OLD_URL, NEW_URL),
);
}
}
}
const rewriter = new HTMLRewriter()
.on("a", new AttributeRewriter("href"))
.on("img", new AttributeRewriter("src"));
const res = await fetch(request);
const contentType = res.headers.get("Content-Type");
// If the response is HTML, it can be transformed with
// HTMLRewriter -- otherwise, it should pass through
if (contentType.startsWith("text/html")) {
return rewriter.transform(res);
} else {
return res;
}
},
};
Defines a delay to be used when incoming requests match your rule. Useful for suspicious requests.
export default {
async fetch(request) {
// Define delay
const delay_in_seconds = 5;
// Introduce a delay
await new Promise((resolve) =>
setTimeout(resolve, delay_in_seconds * 1000),
); // Set delay in milliseconds
// Pass the request to the origin
const response = await fetch(request);
return response;
},
};
Using Snippets and Workers together
While Snippets and Workers have distinct capabilities, they can work together to handle complex traffic workflows.
To avoid conflicts, Snippets and Workers should operate on separate request paths rather than running on the same URL. Have them fetch their respective URLs as a subrequest within their logic, ensuring smooth execution and caching behavior.
Example 1: Passing data between Snippets and WorkersSnippets can modify incoming requests before they reach a Worker, and Workers can read these modifications, perform additional transformations, and pass them downstream.
export default {
async fetch(request) {
// Get the current timestamp
const timestamp = Date.now();
const hexTimestamp = timestamp.toString(16);
// Clone request and add a custom header
const modifiedRequest = new Request(request, {
headers: new Headers(request.headers),
});
modifiedRequest.headers.set("X-Hex-Timestamp", hexTimestamp);
console.log(`X-Hex-Timestamp: ${hexTimestamp}`);
// Pass modified request to origin
return fetch(modifiedRequest);
},
};
Worker: Read a header and add it to the response
export default {
async fetch(request) {
const response = await fetch("https://{snippets_url}", request); // Ensure {snippets_url} points to the endpoint modified by Snippets
const newResponse = new Response(response.body, response);
let hexTimestamp = request.headers.get("X-Hex-Timestamp") || "null";
console.log(hexTimestamp);
newResponse.headers.set("X-Hex-Timestamp", hexTimestamp);
return newResponse;
},
};
Result: The Snippet sets X-Hex-Timestamp
, which the Worker reads and forwards to the origin.
A Worker performs compute-heavy processing (for example, image transformation), while a Snippet serves cached results to avoid unnecessary Worker execution. This can be helpful in situations when running Workers before cache is not desirable.
Worker: Transform and cache responsesexport default {
async fetch(request) {
const url = new URL(request.url);
url.hostname = "origin.example.com"; // Ensure this hostname points to the origin where the resource is hosted
const newRequest = new Request(url, request);
const customKey = `https://${url.hostname}${url.pathname}`; // This custom cache key should be the same in both Worker and Snippet configuration for cache to work
// Fetch and modify response
const response = await fetch(newRequest);
const newResponse = new Response(response.body, response);
// Cache the transformed response
const cache = caches.default;
const cachedResponse = newResponse.clone();
cachedResponse.headers.set("X-Cached-In-Workers", "true");
await cache.put(customKey, cachedResponse);
newResponse.headers.set("X-Retrieved-From-Workers", "true");
return newResponse;
},
};
Snippet: Serve cached responses or forward to Worker
export default {
async fetch(request) {
const url = new URL(request.url);
url.hostname = "origin.example.com"; // Ensure this hostname points to the origin where the resource is hosted
const cacheKey = `https://${url.hostname}${url.pathname}`; // This custom cache key should be the same in both Worker and Snippet configuration for cache to work
// Access cache
const cache = caches.default;
let response = await cache.match(cacheKey);
if (!response) {
console.log(`Cache miss for: ${cacheKey}. Fetching from Worker...`);
url.hostname = "worker.example.com"; // Ensure this hostname points to the Workers route
response = await fetch(new Request(url, request));
// Cache the response for future use
response = new Response(response.body, response);
response.headers.set("Cache-Control", `s-maxage=3600`);
response.headers.set("x-snippets-cache", "stored");
} else {
console.log(`Cache hit for: ${cacheKey}`);
response = new Response(response.body, response);
response.headers.set("x-snippets-cache", "hit");
}
return response;
},
};
Result: The transformed response (X-Cached-In-Workers: true
) is served from cache, avoiding redundant Worker execution (X-Retrieved-From-Workers
is not present). When cache expires, the Snippet fetches a fresh version.
Snippets and Workers share the same Workers runtime, meaning JavaScript code that does not rely on bindings, persistent storage, or advanced execution features can be migrated seamlessly between them.
When to migrate workloads to SnippetsYou should consider migrating a Worker to Snippets if it:
Migrating to Snippets allows you to:
You should migrate from Snippets to Workers if your logic:
If your Snippet reaches the limits of execution time, memory, or functionality, transitioning to Workers ensures your logic can scale without restrictions.
Cloudflare Snippets provide a production-ready solution for fast, declarative edge traffic logic, bridging the gap between Cloudflare Rules and Developer Platform.
Snippets and Workers solve different problems:
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4