Problem with settings CORS policies on R2


I’m using the aws-sdk/client-s3 package for uploading files to my R2 bucket but I have CORS problems. I have tried to set them up using CORSrule and Postman and also I tried using the PutBucketCorsCommand in JS like described in the docs but I still get a “Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.” when trying to use any of the commands the AWS API client proposed.

Anyone has managed to change the CORS of an R2 bucket? My bucket works when uploading files on the server side but I get problems with CORS on the client side.

Any help?


I’m having the same issue, setting the CORS policy via boto3 however it’s not working. I’ve verified my CORS by calling Get Cors API and it seems fine.

Hey, here’s an excellent post on configuring CORS on R2: Configuring CORS on Cloudflare R2

Does following this help?

1 Like

Hi. I am getting the same problem. I followed this process exactly but I still get the missing CORS header issue. However, if I try a GET request then it works. I think the issue is specifically with the preflight response to an OPTIONS request. This does not contain the CORS header.

No this doesn’t. As I said the CORS are changed on the bucket but the endpoint you hit is Cloudflare’s endpoint so the CORS problem comes from here, not from AWS. On a normal bucket, without going through Cloudflare’s endpoint, everything works fine. The CORS can be set up. But since you are “proxying” the bucket, it is useless

I followed this tutorial to the letter, and received a 200 OK response from my S3 API endpoint, but the resources from my bucket still do not have the CORS header
access-control-allow-origin: *
that would be required. I wonder if this is a bug on Cloudflare’s side?

I’m investigating this today.


I’m having the same issue. I’ve also set the CORS policy, and checked it by doing a GET on the ...?cors URL, but when I try an OPTIONS request I get a HTTP/1.1 400 Bad Request response with an empty body.

Did you find any solution? I will get the following error message:

Access to fetch at '' from origin '' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

Hey all, we’ve pushed out a release that fixes quite a few CORS issues.
Specifically, CORS preflight used to return 403s when you signed any additional headers in a presigned URL. That should no longer happen. That may have been the issue most people on here were facing.
There were also issues with wildcards in your CORS config, that are now fixed.

If you’re still seeing issues, this information would help:

  1. Are you using a presigned URL? If so, could you show me how you’re generating it?
  2. If your issue is with preflight requests, what request are you actually sending out? (redacting signatures/secrets is fine)

Hi @siddhant1!

I’m seeing the CORS errors when I try to upload to R2 using a presigned post request. Using using AWS S3 everything works, but when I switch over to R2 I start getting CORS errors.

Here’s what I’m doing:

My server side code is using the @aws-sdk/s3-presigned-post library to generate params needed to post a file upload to R2.

import { S3Client } from '@aws-sdk/client-s3';
import { createPresignedPost } from '@aws-sdk/s3-presigned-post';

let accountId = "XXX";
let accessKeyId = "XXX";
let secretAccessKey = "XXX";

let s3Client = new S3Client({
  credentials: {
  region: "auto",
  endpoint: `https://${accountId}`

let presignedPost = await createPresignedPost(s3Client, {
  Bucket: bucket,
  Key: key,
  Fields: {
    'Content-Type': "image/jpeg",
  Expires: 60 * 60, // 1 hour

Next, the browser is using the data in presignedPost to send the following XHR request to R2:

let file = "..." // the selected jpeg file from the file <input>
let formData = new FormData();

Object.entries({ ...presignedPost.fields, file }).forEach(
  ([field, value]) => {
    formData.append(field, value);

let xhr = new XMLHttpRequest();'POST', presignedPost.url, true);

This sends a POST request to the URL: with the following as form data:

Content-Type: image/jpeg
bucket: XXX
X-Amz-Algorithm: AWS4-HMAC-SHA256
X-Amz-Credential: XXX
X-Amz-Date: 20221201T030934Z
key: XXX
Policy: XXX
X-Amz-Signature: XXX
file: (binary)

And I receive the following error from R2.

Access to XMLHttpRequest at '' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

I have confirmed that my R2 bucket has been setup to allows CORS requests using:

await s3Client.send(
  new PutBucketCorsCommand({
    Bucket: "XXX",
    CORSConfiguration: {
      CORSRules: new Array({
        AllowedHeaders: ["*"],
        // Also tried using:
        // AllowedHeaders: ["Content-Type"],
        AllowedMethods: ["GET", "PUT", "POST", "HEAD"],
        AllowedOrigins: ["*"],
        ExposeHeaders: [],
        MaxAgeSeconds: 3000

I’ve commented out my api keys, account id, bucket name, and other sensitive information with XXX. Just to confirm, this all works when I use S3, but when I switch to R2 I start getting errors.

Hopefully this helps, but please let me know if there’s any more information I should provide.

FWIW I’ve also been trying to get pre-signed PUT urls for R2 with the sig generated in a Worker using aws4fetch working - alas to no success. I’ve followed instructions on
My hunch is that the issue is with aws4fetch, as I also tried this with an S3 bucket but had issues with the sig.

It would be great if there was an end-to-end example of using R2 with pre-signed PUT urls generated in a Worker.

For now I’ve had to switch my project from Workers to Node :frowning:

I’ve also had trouble. I’m trying to upload from localhost. I’ve set a custom domain for the r2 bucket if that matters. I’ve tried use wildcards and explicitly setting http://localhost:4000 as an origin. I’ve tried using an https://#{account_id} url as well as the custom domain url to send the put request to. If I do the former, I get a 400 error. The latter gives a 413. Both say it’s access control problems @siddhant1

const data = await s3.send(new PutBucketCorsCommand({
      Bucket: "assets",
      CORSConfiguration: {
        CORSRules: new Array({
          // AllowedOrigins: ["*"],
          AllowedOrigins: ["http://localhost:4000"],
          AllowedHeaders: ["*"],
          AllowedMethods: ["GET", "PUT", "POST", "HEAD", "DELETE"],
          ExposeHeaders: ["*"],
          MaxAgeSeconds: 3000

You’ve generated a GetObjectCommand and you’re trying to send a PUT request to it - GET !== PUT. This isn’t a CORS issue.

1 Like

You’re right. I’m so sorry, that should’ve been obvious. Thanks for pointing it out!

After several weeks struggling with R2 + CORS (with no success) I managed to get that “access-control-allow-origin=*” on the media files uploaded to R2 in this way:

  1. Connect the R2 bucket to a custom domain (i.e. hosted on Cloudflare
  2. Go to the Domain Dashboard and go to Rules > Transform Rules > Modify Response Header
  3. Create a New Rule: if the Hostname equals to then add the Header “access-control-allow-origin=*”
  4. Deploy

Immediately all assets hosted on began showing that magic header : )


1 Like

Thank you. This has worked.

Thanks, it helped but it is not solution in general as it not able to allow only specific domains (it not works)
I prepared demo page and going to ping devs in Discord.

Same error here; impossible to make CORS work. I tried all above options without success.
Had to switch to DigitalOcean Space which works

FWIW there are now docs (Configure CORS · Cloudflare R2 docs) and a UI to configure CORS, if people are having trouble doing so via the S3 API