Cloudflare R2
Required PUT uploads, endpoint, and CORS for Cloudflare R2.
R2 speaks enough of the S3 API to work with dimah-s3, but it is not a drop-in Amazon S3. Two gaps matter for uploads: no Presigned POST, and no object ACL.
Start from the Quickstart client, then apply the settings below. Skipping any of them usually means browser uploads fail.
Upload method (required)
R2 rejects Presigned POST. Set method: "PUT":
import { dimahS3 } from "@dimah-s3/server";
import { s3Client, defaultBucket } from "@/lib/s3-client";
export const s3 = dimahS3({
s3: s3Client,
defaultBucket,
upload: {
enabled: true,
method: "PUT", // required — R2 rejects Presigned POST
},
download: { enabled: true },
delete: { enabled: true },
});Leaving the default "POST" is the most common R2 failure with dimah-s3.
Endpoint (required)
Fill the Quickstart env vars with R2 values:
S3_REGION=autoS3_ENDPOINT=https://<ACCOUNT_ID>.r2.cloudflarestorage.com
Create an API token with Object Read & Write in the Cloudflare dashboard.
CORS (required)
A valid presigned URL is not enough. Without a CORS policy on the bucket, the browser still blocks the upload:
[
{
"AllowedOrigins": ["https://your-app.example"],
"AllowedMethods": ["GET", "PUT", "HEAD"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag", "Content-Type"],
"MaxAgeSeconds": 3000
}
]Headers on the real request must match what was signed. A mismatched Content-Type often shows up as a 403.
Public access
R2 ignores object ACL. To serve files publicly, turn on bucket public access or attach a custom domain / *.r2.dev URL. Otherwise keep the bucket private and use presigned download. Leave resolveObjectAcl off.
See Cloudflare’s R2 S3 API for the full compatibility matrix.