Upload directly to R2 through its S3-compatible API.
"use client"
import { MiB, r2Adapter } from "@uploadcn/core"
import { demoTransport } from "@/examples/_demo"
import { FileUpload } from "@/components/file-upload"
/**
* R2 speaks the S3 API: the browser code is the same, the server route
* uses `r2Storage({ accountId, bucket, … })`. Multipart kicks in at 32 MB.
*/
const adapter = r2Adapter({
endpoint: "/api/upload",
transport: demoTransport, // docs only, see examples/_demo.ts
multipart: { threshold: 32 * MiB, partSize: 8 * MiB, concurrency: 4 },
})
export default function R2PresignedExample() {
return (
<FileUpload adapter={adapter} title="Upload directly to Cloudflare R2" />
)
}
R2 implements the S3 API, so the browser code is identical to S3, r2Adapter is an
alias of s3Adapter. The difference is on the server.
Server
import { createUploadRoute, r2Storage } from "@uploadcn/server"
export const { POST } = createUploadRoute({
storage: r2Storage({
accountId: process.env.R2_ACCOUNT_ID!,
bucket: process.env.R2_BUCKET!,
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
// A custom domain or r2.dev URL for reading objects:
publicUrl: process.env.R2_PUBLIC_URL,
}),
})r2Storage uses https://<account>.r2.cloudflarestorage.com, region auto and
path-style URLs. For jurisdiction-restricted buckets pass jurisdiction: "eu".
@uploadcn/server signs with Web Crypto, so the same route runs on Cloudflare Workers.
CORS
In the R2 dashboard, under Settings → CORS policy:
[
{
"AllowedOrigins": ["https://your-app.com"],
"AllowedMethods": ["PUT"],
"AllowedHeaders": ["content-type"],
"ExposeHeaders": ["ETag"]
}
]Client
import { r2Adapter } from "@uploadcn/core"
const adapter = r2Adapter({ endpoint: "/api/upload" })