R2: Unable to set Content-Type on presigned url XHR upload

I’m not able to set the Content-Type or mimetype on R2 presigned url uploads using XHR. In the Cloudflare dashboard all my uploads show “multipart/form-data; boundary=–…” when not specifying the Content-Type header. Adding the XHR header returns CORS error. I’ve configured CORS as shown below.

Client-side upload

const formData = new FormData();
formData.append("file", file); // File interface
const xhr = new XMLHttpRequest();
xhr.setRequestHeader("Content-Type", file.type); // returns CORS error
xhr.open("PUT", url, true); // signature.url
xhr.send(formData);

I generate my R2 presigned URLs on a Worker using aws4fetch.

const aws = new AwsClient({
    accessKeyId: env.CLOUDFLARE_R2_KEY,
    secretAccessKey: env.CLOUDFLARE_R2_SECRET,
    region: "auto",
    service: "s3"
});

const signature = await aws.sign(`https://${env.CLOUDFLARE_R2_BUCKET_NAME}.${env.CLOUDFLARE_ACCOUNT_ID}.r2.cloudflarestorage.com/${filename}`, {
  aws: { signQuery: true },
  method: "PUT"
});

// signature.url

CORS rule for PUT requests.

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <CORSRule>
        <AllowedMethod>PUT</AllowedMethod>
        <AllowedHeader>*</AllowedHeader>
        <AllowedOrigin>*</AllowedOrigin>
    </CORSRule>
</CORSConfiguration>

I’ve also tried appending Content-Type and/or mimeType to formData without any luck. Any help is greatly appreciated.

formData.append("Content-Type", file.type);
formData.append("mimeType", file.type);

Changing AllowedHeader from * to content-type solved my issue as suggested on Discord.

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <CORSRule>
        <AllowedMethod>PUT</AllowedMethod>
        <AllowedHeader>content-type</AllowedHeader>
        <AllowedOrigin>*</AllowedOrigin>
    </CORSRule>
</CORSConfiguration>

I also switched to AwsV4Signer but I don’t think that made any difference.

Notably this is a bug and won’t be required when fixed - * should work.

1 Like