Snippet
Learn how to deploy, manage, and execute lightweight JavaScript code at the edge with Cloudflare Snippets for request/response modification.
Execute lightweight JavaScript code at the edge. Cloudflare Snippets run directly on Cloudflare’s network to modify HTTP requests and responses without touching your origin server.
Quick Start
Section titled “Quick Start”Deploy a basic snippet that modifies response headers:
import { Snippet } from "alchemy/cloudflare";
const snippet = await Snippet("cors-handler", { zone: "example.com", // Zone ID, hostname, or Zone resource name: "cors-handler", script: `export default { async fetch(request) { const response = await fetch(request); response.headers.set("Access-Control-Allow-Origin", "*"); return response; }} `.trim(),});This creates a snippet that:
- Runs on every request to your zone
- Adds a CORS header to responses
- Takes < 5ms to execute
- Requires no changes to your origin
Snippet Configuration
Section titled “Snippet Configuration”Configure all aspects of your snippet including zone targeting, code source, and adoption behavior:
import { Snippet } from "alchemy/cloudflare";
const snippet = await Snippet("my-snippet", { zone: "example.com", // Zone ID, hostname, or Zone resource (required) name: "my_snippet", // Snippet name (defaults to ${app}-${stage}-${id} with underscores)
// Code source - provide exactly one of these: script: ` // Inline JavaScript codeexport default { async fetch(request) { const response = await fetch(request); response.headers.set("X-Custom-Header", "value"); return response; }} `.trim(),
// OR entrypoint: "./src/snippets/auth.js", // Path to JavaScript file
// Optional configuration adopt: true, // Adopt existing snippet with same name (default: false)
// Cloudflare API credentials (optional - uses environment variables by default) accountId: "your-account-id", // Cloudflare account ID (default: CLOUDFLARE_ACCOUNT_ID env var) apiToken: "your-api-token", // Cloudflare API token (default: CLOUDFLARE_API_TOKEN env var)});Important Notes:
⚠️ Either script or entrypoint must be provided (not both). Use script for inline code or entrypoint for file-based snippets.
Name Format Rules:
- Only lowercase letters (a-z), numbers (0-9), and underscores (_)
- Max 50 characters
- Hyphens in default names are automatically converted to underscores
Execution Order
Section titled “Execution Order”Snippets execute after these Cloudflare features in this order:
- Single Redirects
- URL Rewrite Rules
- Configuration Rules
- Origin Rules
- Bulk Redirects
- Managed Transforms
- Request Header Transform Rules
- Cache Rules
This means Snippets see requests after they’ve been processed by these rules.
Zone Specification
Section titled “Zone Specification”Specify the zone where your snippet runs using any format:
// Zone ID (fastest - no lookup needed)await Snippet("snippet-1", { zone: "023e105f4ecef8ad9ca31a8372d0c353", name: "snippet-1", script: "export default { async fetch(r) { return fetch(r); } }",});
// Zone hostname (auto-resolved)await Snippet("snippet-2", { zone: "example.com", name: "snippet-2", script: "export default { async fetch(r) { return fetch(r); } }",});
// Zone resource (Alchemy composition)const zone = await Zone("main", { name: "example.com", type: "full" });await Snippet("snippet-3", { zone, name: "snippet-3", script: "export default { async fetch(r) { return fetch(r); } }",});Basic Snippet Examples
Section titled “Basic Snippet Examples”Add Security Headers
Section titled “Add Security Headers”const securityHeaders = await Snippet("security-headers", { zone: "example.com", name: "security-headers", script: `export default { async fetch(request) { const response = await fetch(request);
response.headers.set("X-Frame-Options", "DENY"); response.headers.set("X-Content-Type-Options", "nosniff"); response.headers.set("X-XSS-Protection", "1; mode=block"); response.headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
return response; }} `.trim(),});JWT Validation
Section titled “JWT Validation”const jwtValidator = await Snippet("jwt-validator", { zone: "example.com", name: "jwt-validator", script: `export default { async fetch(request) { const token = request.headers.get("Authorization")?.replace("Bearer ", "");
if (!token) { return new Response("Unauthorized", { status: 401 }); }
// Validate JWT (simplified - use a proper JWT library in production) try { const parts = token.split("."); if (parts.length !== 3) throw new Error("Invalid JWT format");
// In production, verify the signature const payload = JSON.parse(atob(parts[1]));
// Continue to origin const response = await fetch(request); response.headers.set("X-User-ID", payload.sub);
return response; } catch (error) { return new Response("Invalid token", { status: 403 }); } }} `.trim(),});Custom Redirects
Section titled “Custom Redirects”const redirector = await Snippet("smart-redirects", { zone: "example.com", name: "smart-redirects", script: `export default { async fetch(request) { const url = new URL(request.url);
// Redirect old URLs to new location if (url.pathname.startsWith("/old-blog/")) { const newPath = url.pathname.replace("/old-blog/", "/blog/"); return Response.redirect(url.origin + newPath, 301); }
// Mobile redirect if (request.headers.get("User-Agent")?.includes("Mobile")) { if (!url.hostname.startsWith("m.")) { return Response.redirect("https://m." + url.hostname + url.pathname, 302); } }
return fetch(request); }} `.trim(),});Load from File
Section titled “Load from File”Store your snippet code in a separate file for easier management:
import { readFile } from "node:fs/promises";import { resolve } from "node:path";
const scriptContent = await readFile( resolve("./src/snippets/auth-handler.js"), "utf-8");
const authSnippet = await Snippet("auth-handler", { zone: "example.com", name: "auth-handler", script: scriptContent,});Snippet file: ./src/snippets/auth-handler.js
export default { async fetch(request) { const token = request.headers.get("Authorization"); if (!token) { return new Response("Unauthorized", { status: 401 }); } return fetch(request); }}Performance Constraints
Section titled “Performance Constraints”Snippets have strict limits to maintain edge performance:
| Limit | Value |
|---|---|
| Execution time | 5 ms |
| Memory | 2 MB |
| Total code size | 32 KB |
| Subrequests | 2-5 (by plan) |
Plan limits:
- Pro: 25 snippets/zone, 2 subrequests
- Business: 50 snippets/zone, 3 subrequests
- Enterprise: 300 snippets/zone, 5 subrequests
Keep your code lightweight:
// ❌ Too slow - expensive computationexport default { async fetch(request) { const result = await expensiveQuery(); // Don't do this return new Response(result); }}
// ✅ Lightweight - fast headers and redirectsexport default { async fetch(request) { if (request.url.includes("/admin")) { return Response.redirect("/"); } return fetch(request); }}Adoption Pattern
Section titled “Adoption Pattern”Already have snippets? Adopt them instead of failing:
const existing = await Snippet("legacy-snippet", { zone: "example.com", name: "legacy-snippet", // Must match existing snippet name script: "...", adopt: true, // Use existing if found, don't error});Validation & Error Handling
Section titled “Validation & Error Handling”Snippets validate name format before deployment:
// ❌ Invalid - contains hyphensawait Snippet("bad-name", { zone: "example.com", name: "bad-name", // Error: invalid characters script: "...",});
// ✅ Valid - lowercase, letters, numbers, underscoresawait Snippet("auth_handler", { zone: "example.com", name: "auth_handler", script: "...",});Valid name format:
- Lowercase letters (a-z)
- Numbers (0-9)
- Underscores (_)
- Max 50 characters
Binding to Workers
Section titled “Binding to Workers”Use snippets in Workers via environment references:
const authSnippet = await Snippet("auth-checker", { zone: "example.com", name: "auth-checker", script: "export default { async fetch(r) { return fetch(r); } }",});
export const worker = await Worker("api", { entrypoint: "./src/api.ts", bindings: { SNIPPET_NAME: "auth-checker", // Reference by name }});Then access in your worker:
export default { async fetch(request: Request, env: typeof worker.Env) { console.log(`Using snippet: ${env.SNIPPET_NAME}`); return new Response("OK"); }}Working with Snippet Rules
Section titled “Working with Snippet Rules”Snippets without rules do nothing. Use SnippetRule to control when they execute:
import { Snippet, SnippetRule } from "alchemy/cloudflare";
// Create snippetconst snippet = await Snippet("cache-buster", { zone: "example.com", name: "cache-buster", script: `export default { async fetch(request) { const response = await fetch(request); response.headers.set("Cache-Control", "no-cache"); return response; }} `.trim(),});
// Attach execution rulesconst rules = await SnippetRule("cache-rules", { zone: "example.com", rules: [ { expression: 'http.request.uri.path eq "/api"', snippet, // Reference the snippet resource description: "Bypass cache for API endpoints", enabled: true, }, ],});Advanced Patterns
Section titled “Advanced Patterns”Conditional Response Caching
Section titled “Conditional Response Caching”Cache responses based on custom logic:
const responseCacher = await Snippet("smart-cache", { zone: "example.com", name: "smart-cache", script: `export default { async fetch(request) { const url = new URL(request.url); const response = await fetch(request);
// Don't cache error responses if (response.status >= 400) { return response; }
// Cache GET requests for 1 hour if (request.method === "GET") { response.headers.set("Cache-Control", "public, max-age=3600"); }
// Don't cache mutations if (["POST", "PUT", "DELETE"].includes(request.method)) { response.headers.set("Cache-Control", "no-cache"); }
return response; }} `.trim(),});Request Transformation
Section titled “Request Transformation”Modify incoming requests before they reach your origin:
const requestTransformer = await Snippet("request-modifier", { zone: "example.com", name: "request-modifier", script: `export default { async fetch(request) { // Normalize headers const newRequest = new Request(request, { headers: new Headers(request.headers), });
newRequest.headers.set("X-Forwarded-Proto", "https"); newRequest.headers.set("X-Forwarded-Host", request.headers.get("Host"));
// Add request ID newRequest.headers.set("X-Request-ID", crypto.randomUUID());
return fetch(newRequest); }} `.trim(),});Request/Response Logging
Section titled “Request/Response Logging”Log requests/responses for debugging:
const logger = await Snippet("request-logger", { zone: "example.com", name: "request-logger", script: `export default { async fetch(request) { const start = performance.now(); const response = await fetch(request); const duration = performance.now() - start;
// Log to console (visible in Cloudflare Logs) console.log(\`\${request.method} \${request.url} - \${response.status} (\${duration.toFixed(2)}ms)\`);
return response; }} `.trim(),});Debugging Snippets
Section titled “Debugging Snippets”Test with SnippetRule
Section titled “Test with SnippetRule”Use rule expressions to test snippet behavior on specific traffic:
const testSnippet = await Snippet("debug-snippet", { zone: "example.com", name: "debug-snippet", script: `export default { async fetch(request) { const response = await fetch(request); // Add debugging headers response.headers.set("X-Snippet-Executed", "true"); response.headers.set("X-Request-Time", new Date().toISOString()); return response; }} `.trim(), adopt: true,});
// Test on specific paths onlyconst rules = await SnippetRule("debug-rules", { zone: "example.com", rules: [ { expression: 'http.request.uri.path eq "/debug"', // Only test path snippet: testSnippet, enabled: true, }, ],});Performance Monitoring
Section titled “Performance Monitoring”Monitor snippet execution in your logs:
const monitored = await Snippet("perf-monitor", { zone: "example.com", name: "perf-monitor", script: `export default { async fetch(request) { const start = Date.now(); const response = await fetch(request); const duration = Date.now() - start;
if (duration > 100) { console.warn(\`Slow response: \${duration}ms for \${request.url}\`); }
return response; }} `.trim(),});Troubleshooting
Section titled “Troubleshooting”Snippet Not Executing
Section titled “Snippet Not Executing”Snippet created but not running? Check the rules:
// ❌ No rules = no executionawait Snippet("orphaned", { zone: "example.com", name: "orphaned", script: "...",});
// ✅ Attach execution rulesconst snippet = await Snippet("active", { zone: "example.com", name: "active", script: "...",});
await SnippetRule("rules", { zone: "example.com", rules: [{ expression: "true", snippet }], // Now it runs});Performance Timeout (5ms Exceeded)
Section titled “Performance Timeout (5ms Exceeded)”Snippet execution takes too long:
Common causes:
- Multiple subrequests - Keep fetch calls under limit
- Complex regex - Use simple string matching instead
- Large payloads - Process in chunks, not all at once
Fix:
// ❌ Too slow - multiple fetchesexport default { async fetch(request) { const r1 = await fetch("https://api.example.com/1"); const r2 = await fetch("https://api.example.com/2"); return new Response(JSON.stringify({ r1, r2 })); }}
// ✅ Fast - single operationexport default { async fetch(request) { const url = new URL(request.url); if (url.pathname === "/api") { return new Response("cached", { headers: { "Cache-Control": "max-age=3600" } }); } return fetch(request); }}Memory Issues (2MB Exceeded)
Section titled “Memory Issues (2MB Exceeded)”Snippet hits memory limit:
Common causes:
- Large response buffering - Stream instead of buffer
- Unnecessary data structures - Keep code minimal
- Base64 encoding - Use binary formats
Fix:
// ❌ Buffers entire responseexport default { async fetch(request) { const response = await fetch(request); const body = await response.text(); // Loads entire body into memory return new Response(body); }}
// ✅ Streams responseexport default { async fetch(request) { return fetch(request); // Streams directly to client }}Code Size Limit (32KB Exceeded)
Section titled “Code Size Limit (32KB Exceeded)”Your snippet is too large:
Common causes:
- Inline data - Move to separate file or KV store
- Unused libraries - Trim dependencies
- Comments/formatting - Minify before deploy
Fix:
// Split large snippets into multiple smaller onesconst snippet1 = await Snippet("auth", { zone: "example.com", name: "auth", script: "/* auth only */",});
const snippet2 = await Snippet("headers", { zone: "example.com", name: "headers", script: "/* headers only */",});
await SnippetRule("orchestration", { zone: "example.com", rules: [ { expression: 'http.request.uri.path eq "/auth"', snippet: snippet1 }, { expression: "true", snippet: snippet2 }, ],});Invalid Characters in Name
Section titled “Invalid Characters in Name”Snippet name validation error:
// ❌ Invalid charactersawait Snippet("bad", { zone: "example.com", name: "my-snippet", // Error: hyphens not allowed script: "...",});
// ✅ Valid nameawait Snippet("good", { zone: "example.com", name: "my_snippet", // Letters, numbers, underscores only script: "...",});Best Practices
Section titled “Best Practices”- Keep snippets small - One concern per snippet for easier testing
- Use rules to target traffic - Don’t waste CPU on requests that don’t need the snippet
- Cache aggressively - Let Cloudflare cache static responses
- Test edge cases - Test with actual traffic patterns before production
- Monitor performance - Track execution time and optimize slow paths
- Version your code - Keep git history of snippet deployments
- Avoid sync operations - Always use async/await for I/O
- Use secrets for sensitive data - Don’t hardcode API keys
- Plan availability - Snippets are only available on Pro, Business, and Enterprise plans.
- No version rollback - Cloudflare doesn’t support versioning for Snippets. Test carefully before deploying changes.
Next steps
Section titled “Next steps”- Snippet Rules: See SnippetRule for execution control
- Workers: See Worker for more complex edge logic
- Zones: See Zone for zone management