Cloudflare R2

Cloudflare R2 over the S3-compatible HTTP API. Auto-loads R2_* env vars or accepts an R2Bucket binding inside Workers.

Installation

@aws-sdk/client-s3, @aws-sdk/s3-presigned-post, and @aws-sdk/s3-request-presigner are optional peer dependencies of files-sdk - install alongside the SDK so the adapter's imports resolve at runtime.

npm install files-sdk @aws-sdk/client-s3 @aws-sdk/s3-presigned-post @aws-sdk/s3-request-presigner

Cloudflare R2 over the S3-compatible HTTP API. Auto-loads from R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY. Inside Cloudflare Workers you can pass an R2Bucket binding directly instead.

import { Files } from "files-sdk";import { r2 } from "files-sdk/r2";const files = new Files({  adapter: r2({    bucket: "uploads",    accountId: process.env.R2_ACCOUNT_ID!,    // accessKeyId / secretAccessKey auto-loaded    // from R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEY  }),});

publicBaseUrl - optional, an r2.dev subdomain or custom domain bound to the bucket. When set, url() returns `${publicBaseUrl}/${key}` and skips signing.

Hybrid: binding + HTTP credentials

Inside a Worker, you can pass both a binding and HTTP credentials. Reads and writes go through the binding (no egress, no extra round trip); url() and signedUploadUrl() route through the HTTP signer because a Worker binding has no signing primitive. The S3 client is lazy-loaded - bindings-only Workers don't pull @aws-sdk/client-s3 into their bundle.

// Inside a Cloudflare Worker. The binding handles uploads/downloads// (intra-Worker, no egress fees). The HTTP credentials let url() and// signedUploadUrl() sign presigned URLs the binding alone can't produce.const files = new Files({  adapter: r2({    binding: env.UPLOADS,    bucket: "uploads",    accountId: env.R2_ACCOUNT_ID,    accessKeyId: env.R2_ACCESS_KEY_ID,    secretAccessKey: env.R2_SECRET_ACCESS_KEY,  }),});