How to Solve CORS Errors When Using Cloudflare R2 for File Uploads

If you’ve ever tried to upload or access files from a Cloudflare R2 bucket using a browser-based or client-side app and hit a wall with CORS (Cross-Origin Resource Sharing) errors, you’re not alone. This is a common challenge developers face when using object storage systems like R2 in modern web stacks.

In this article, we’ll break down why these errors occur, what’s happening under the hood, and how to solve them correctly—whether you’re working from a frontend interface, server-side app, or cloud service.

The Scenario

Let’s say you’re building a file upload system. Your app is deployed at:

https://myapp.example

And you’re storing uploaded media in a Cloudflare R2 bucket via signed URLs. Everything seems correct—your app has permission to write, you’ve generated the right signature, and the URL works if pasted in a tool like curl.

But in the browser, your upload request fails with an error like this:

Access to XMLHttpRequest at 'https://<r2-bucket-url>' from origin 'https://myapp.example' has been blocked by CORS policy: 
No 'Access-Control-Allow-Origin' header is present on the requested resource.

Or, from the JavaScript console:

Response to preflight request doesn't pass access control check: 
No 'Access-Control-Allow-Origin' header is present.

This is a CORS issue, not an authentication or signature problem.

What Is CORS and Why It’s Blocking You

CORS (Cross-Origin Resource Sharing) is a security feature built into all modern browsers. It restricts JavaScript running on one origin (e.g. https://myapp.example) from making requests to another origin (e.g. https://my-bucket.r2.cloudflarestorage.com) unless the target explicitly allows it.

When a browser tries to send a non-simple HTTP request like PUT, POST, or DELETE to a different domain, it first sends a preflight OPTIONS request. The storage service must reply with proper CORS headers like:

Access-Control-Allow-Origin: https://myapp.example
Access-Control-Allow-Methods: PUT

If those headers aren’t present in the response, the browser blocks the main request before it ever reaches R2.

Why Cloudflare R2 Blocks These Requests by Default

Cloudflare R2 is secure by design. Out of the box, R2 does not include any CORS headers. This means all cross-origin requests will be blocked, even if the URL is valid and signed.

Unless you explicitly configure your bucket’s CORS policy, any direct upload or download request from a web app or external service will fail with CORS errors.

The Solution: Set the Correct CORS Policy on Your R2 Bucket

To enable access to your R2 bucket from a browser or another origin, you need to add a CORS policy under your bucket's settings in the Cloudflare dashboard.

Option 1: Allow Access from All Origins

If your use case involves public uploads or file access (e.g., a static file CDN or public image host), and you’re okay with any domain making requests:

[
  {
    "AllowedHeaders": ["*"],
    "AllowedMethods": ["GET", "PUT", "POST", "DELETE", "HEAD"],
    "AllowedOrigins": ["*"],
    "ExposeHeaders": ["ETag"],
    "MaxAgeSeconds": 3600
  }
]

This allows requests from any origin. Use with care.

Option 2: Restrict to a Specific Domain

If your app is hosted at https://myapp.example, and you want to only allow that domain:

[
  {
    "AllowedHeaders": ["*"],
    "AllowedMethods": ["GET", "PUT", "POST", "DELETE", "HEAD"],
    "AllowedOrigins": ["https://myapp.example"],
    "ExposeHeaders": ["ETag"],
    "MaxAgeSeconds": 3600
  }
]

This setup is more secure and is recommended for production systems where file operations should only be performed by trusted clients.

How to Set This in the Cloudflare Dashboard

  1. Log in to your Cloudflare account.

  2. Go to R2 → Select your bucket.

  3. Click on Settings.

  4. Scroll to the CORS Configuration section.

  5. Paste the appropriate JSON configuration.

  6. Save changes.

Your CORS configuration will take effect immediately.

What About Backend Uploads?

If you’re uploading from your server (e.g., PHP, Laravel, Node.js, etc.), CORS is not an issue. CORS only applies to browser-based requests. So if you proxy the upload through your backend, you can avoid the entire CORS issue.

For example:

  • Client → POSTs file to your Laravel API

  • Laravel handles upload to Cloudflare R2 using SDK or signed URL

  • R2 responds to your server (no browser involved)

This gives you full control over request headers, validation, and security.

Summary

CORS errors when working with Cloudflare R2 are common, but entirely fixable. Here's a quick recap:

  • CORS is a browser security mechanism to prevent unauthorized cross-origin requests.

  • Cloudflare R2 requires you to explicitly define CORS rules to allow uploads/downloads from web apps.

  • You can allow all origins (*) or specific ones (https://myapp.example).

  • Use a backend proxy if you need tighter control or want to avoid exposing signed URLs to the frontend.

By understanding how CORS works and configuring it properly on your R2 bucket, you can unlock seamless integrations between your cloud storage and browser applications.

10
Subscribe to my newsletter

Read articles from Babatunde Daramola directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Babatunde Daramola
Babatunde Daramola

Over a decade in web development | PHP | Laravel | Js | Vue | React | nodejs | Educator. More = https://linktr.ee/ritechoice23