Fix Cloudflare R2 CORS Error: Preflight, Origin, Headers, and ETag
Troubleshoot Cloudflare R2 CORS errors in browser apps. Fix missing Access-Control-Allow-Origin, failed preflight OPTIONS, blocked Content-Type or x-amz-* headers, localhost origins, and hidden ETag response headers.
R2 CORS errors are usually one missing origin, method, or header
Cloudflare R2 CORS failures look noisy in the browser, but most reduce to a small mismatch: the frontend origin is not allowed, the method is missing, a request header is blocked, or a response header is not exposed. The S3/R2 CORS debugger checks those pieces directly.
Start with the exact browser error
Do not debug R2 CORS from the backend first. Open browser DevTools, go to Network, and find the failed request. If there is an OPTIONS request before your upload or download, that is the preflight check. Copy the request headers and the console error text.
R2 CORS error quick fixes
| Error text | What to check | Policy fix |
|---|---|---|
| No Access-Control-Allow-Origin header | Origin request header | Add the exact origin to AllowedOrigins |
| Response to preflight request does not pass | OPTIONS request method | Add the intended method to AllowedMethods |
| Request header field Content-Type is not allowed | Access-Control-Request-Headers | Add Content-Type to AllowedHeaders |
| Request header field x-amz-acl is not allowed | Upload ACL or metadata headers | Add x-amz-acl or x-amz-* to AllowedHeaders |
| ETag is missing in JavaScript | Response headers app reads | Add ETag to ExposeHeaders |
Checklist for fixing R2 CORS
- Copy the frontend origin exactly:
https://app.example.comis different fromhttp://localhost:3000. - Check
Access-Control-Request-Method. Uploads usually needPUTorPOST. - Check
Access-Control-Request-Headers. Add every listed header toAllowedHeaders. - If your app reads
ETag, add it toExposeHeaders. - If cookies or credentials are involved, avoid
AllowedOrigins: ["*"]. - After changing CORS, retest in the browser and watch for cached CDN responses.
Example fixed R2 policy
This example covers a common app upload flow: local development, production app origin, PUT/POST uploads, Content-Type, x-amz metadata headers, and JavaScript access to ETag.
[
{
"AllowedOrigins": [
"https://app.example.com",
"http://localhost:3000"
],
"AllowedMethods": ["PUT", "POST", "HEAD"],
"AllowedHeaders": [
"Content-Type",
"x-amz-*"
],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3600
}
]Why localhost often breaks
CORS origins include scheme, host, and port. If your policy allows https://app.example.com, it does not allow http://localhost:3000. Add each development origin explicitly, and remove it later if your production policy should be stricter.
Debug R2 CORS with a preflight curl
A normal curl request will not show the browser's CORS decision. Use an OPTIONS request with Origin, Access-Control-Request-Method, and Access-Control-Request-Headers.
curl -i -X OPTIONS 'https://files.example.com/avatar.png' \
-H 'Origin: https://app.example.com' \
-H 'Access-Control-Request-Method: PUT' \
-H 'Access-Control-Request-Headers: Content-Type,x-amz-meta-user-id'Use the R2 CORS debugger
Paste the current policy into Spoold's CORS debugger. Enter the request origin, method, request headers, response headers your app reads, and error text. The debugger returns a diagnosis and a suggested fixed config.
Related Tools
Related Articles
Cloudflare R2 CORS Generator: Create Bucket CORS JSON for Browser Apps
Generate Cloudflare R2 CORS JSON for public reads, browser uploads, signed downloads, and presigned URL workflows. Learn AllowedOrigins, AllowedMethods, AllowedHeaders, ExposeHeaders, MaxAgeSeconds, and Wrangler-ready config.
R2 Presigned URL CORS: Fix Browser Upload and Download Errors
Cloudflare R2 presigned URLs still need bucket CORS when used from a browser. Learn how to allow PUT, POST, GET, Content-Type, x-amz-* headers, ETag exposure, localhost origins, and preflight requests.
S3 CORS Policy Generator: Create AWS Bucket CORS JSON for Browser Uploads
Generate AWS S3 CORS policies for browser uploads, public reads, signed downloads, and presigned URLs. Learn CORSRules, AllowedOrigins, AllowedMethods, AllowedHeaders, ExposeHeaders, MaxAgeSeconds, and put-bucket-cors.