# Cloudflare R2 (https://s3.dimah.dev/docs/providers/cloudflare-r2)



Cloudflare R2 is compatible with S3 APIs with two key differences: it requires Presigned `PUT` (it does not support Presigned `POST`), and it ignores object ACLs.

<div className="fd-steps">
  <div className="fd-step">
    ## Configure upload method [#1-configure-upload-method]

    Set `upload: { method: "PUT" }`:

    ```ts title="lib/s3.ts"
    import { S3Client } from "@aws-sdk/client-s3";
    import { dimahS3, route } from "@dimah-s3/server";

    export const awsS3 = new S3Client({
      region: "auto",
      endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
      credentials: {
        accessKeyId: process.env.R2_ACCESS_KEY_ID!,
        secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
      },
    });

    export const s3 = dimahS3({
      client: awsS3,
      bucket: process.env.R2_BUCKET!,
      routes: {
        avatar: route({
          upload: {
            method: "PUT", // Required for Cloudflare R2
            fileTypes: ["image/*"],
            maxFileSize: 2 * 1024 * 1024,
          },
        }),
      },
    });
    ```
  </div>

  <div className="fd-step">
    ## Bucket CORS policy [#2-bucket-cors-policy]

    Add a CORS rule in the Cloudflare R2 dashboard for your frontend domain:

    ```json
    [
      {
        "AllowedOrigins": ["https://your-app.example"],
        "AllowedMethods": ["GET", "PUT", "HEAD"],
        "AllowedHeaders": ["*"],
        "ExposeHeaders": ["ETag", "Content-Type"],
        "MaxAgeSeconds": 3000
      }
    ]
    ```
  </div>
</div>

## Frequently asked questions [#frequently-asked-questions]

<Accordions>
  <Accordion title="How do I serve R2 files publicly?">
    Enable the public access toggle in the R2 bucket settings or connect a custom domain (e.g. `cdn.example.com`). Construct your image URLs directly: `https://cdn.example.com/{avatarKey}`.
  </Accordion>
</Accordions>
