Handling CORS in Workers: A Developer's Guide to Secure APIs
Learn how to manage CORS in Cloudflare Workers. Master preflight requests, restrict origins, and secure your API headers for production-grade cross-origin access.

Previously in this course, we performed a final production audit to ensure our infrastructure was performant and secure. Now that our backend is hardened, we need to address a common friction point for browser-based clients: Cross-Origin Resource Sharing, or CORS.
If you have ever tried to fetch data from an API hosted on a different domain than your frontend, you’ve likely encountered the dreaded "CORS error." This happens because browsers enforce a security sandbox that prevents web pages from making requests to a domain other than the one that served the page, unless the server explicitly allows it.
Understanding CORS from First Principles
Browsers use CORS to protect users from malicious sites that might try to access data on behalf of a logged-in user. When a browser detects a cross-origin request, it evaluates the response headers from your Worker. If the correct headers aren't present, the browser blocks the response.
There are two types of requests in this flow:
- Simple Requests: Standard GET or POST requests that don't trigger a preflight.
- Preflight Requests: For complex requests (like those with custom headers or
application/jsoncontent types), the browser sends anOPTIONSrequest first. Your Worker must respond to thisOPTIONSrequest with the allowed origins and methods before the actual request is sent.
Configuring CORS Headers in Workers
To handle CORS in a Worker, you must intercept the request and return the appropriate headers. We typically define a constant for these headers to keep our code DRY.
JAVASCRIPTconst corsHeaders = { "Access-Control-Allow-Origin": "https://your-frontend.com", // Restrict to your domain "Access-Control-Allow-Methods": "GET,HEAD,POST,OPTIONS", "Access-Control-Max-Age": "86400", "Access-Control-Allow-Headers": "Content-Type, Authorization", }; export default { async fetch(request, env, ctx) { // 1. Handle Preflight (OPTIONS) requests if (request.method === "OPTIONS") { return new Response(null, { headers: { ...corsHeaders, "Access-Control-Allow-Headers": request.headers.get("Access-Control-Request-Headers") || "", }, }); } // 2. Handle actual requests const response = await fetch(request); const newResponse = new Response(response.body, response); // Append headers to the response Object.keys(corsHeaders).forEach((key) => { newResponse.headers.set(key, corsHeaders[key]); }); return newResponse; }, };
The Preflight Logic
The OPTIONS request acts as a "permission check." If you don't respond to this correctly, the browser will never send your POST or PUT request. Note how in the example above, we dynamically return the headers requested by the browser during the preflight phase.
| Header | Purpose |
|---|---|
Access-Control-Allow-Origin | Specifies which domains can access your resources. |
Access-Control-Allow-Methods | Lists HTTP verbs allowed for the endpoint. |
Access-Control-Allow-Headers | Lists custom headers allowed during the request. |
Access-Control-Max-Age | Tells the browser how long to cache the preflight result. |
Hands-on Exercise: Restricting Origins
Your current task is to modify your existing Worker to restrict access.
- Identify your production frontend domain (e.g.,
https://myapp.pages.dev). - Update your
corsHeadersobject to use that specific domain instead of a wildcard (*). - Deploy your Worker and use the browser's Network tab to confirm that the
Access-Control-Allow-Originheader matches your domain exactly.
Common Pitfalls
- Using
*in Production: Never useAccess-Control-Allow-Origin: *if your API handles user credentials (cookies or Authorization headers). Browsers will reject the request if the origin is a wildcard when credentials are required. - Forgetting
OPTIONS: If your API works in Postman but fails in the browser, you are likely missing theOPTIONShandler. - Header Case Sensitivity: While HTTP headers are case-insensitive, it is best practice to follow standard naming conventions to avoid issues with older proxies or strict middleware.
FAQ
Q: Can I allow multiple origins?
A: CORS only supports a single origin in the Access-Control-Allow-Origin header. If you need multiple, you must check the Origin header of the incoming request and dynamically set the Access-Control-Allow-Origin header to match if it exists in your "allowed" list.
Q: Why do I need Access-Control-Max-Age?
A: It prevents the browser from sending an OPTIONS request for every single API call, significantly improving performance by caching the preflight result.
Q: Is CORS a security feature?
A: It is a browser-side policy. It prevents browser-based attacks, but it does not protect your API from direct access via tools like curl or Postman. You must still implement proper Authentication Fundamentals to secure your data.
Recap
We've moved beyond simple request handling by implementing a robust CORS strategy. By managing OPTIONS requests and strictly defining allowed origins, we’ve hardened our API against cross-site scripting risks while ensuring our frontend can communicate securely with our Workers. As we learned in our Dynamic Header Manipulation lesson, controlling response headers is a critical skill for any platform engineer.
Up next: We will begin working with KV Storage to implement fast, distributed key-value data access.
Work with me

Laravel REST API Development
Clean, secure, well-documented Laravel REST APIs — the backend engine for your app, mobile client, or SaaS. Built by an API specialist.

Custom Email & File Storage System on Cloudflare (Google Workspace Alternative)
Your own private email + file storage suite on your domain — unlimited mailboxes, no per-seat fees. A self-owned Google Workspace alternative for a flat ~$5/month.


