Skip to main content
CloudflareStorage is the Storage component’s Cloudflare R2 backend. It extends the shared Storage base class, which talks to R2 through Bun’s built-in S3 client (R2 is S3-compatible), so you get production object storage with zero egress fees behind the same IStorage interface as every other backend. Switch to or from R2 without changing a single call site.

What R2 brings here

Because R2 speaks S3, the backend runs on Bun’s native S3Client and CloudflareStorage only supplies the connection options. The interface is the same IStorage you use elsewhere: put, getFile, getAsJson, delete, list, and the rest. All transport logic lives in the Storage base class, so the subclass provides only credentials and getOptions(). put() accepts strings, ArrayBuffer, Blob, Request/Response, and BunFile/S3File. The class registers with @decorator.storage() and resolves from the container.

Installation

CloudflareStorage ships with @talosjs/storage. It uses Bun’s built-in S3 client, so there’s no extra SDK to install.

Environment variables

All three required values are validated when CloudflareStorage is constructed, so missing credentials fail fast at startup.

How it works

CloudflareStorage reads its credentials, then getOptions() returns the S3Options the base Storage class uses to build a Bun.S3Client. Calling setBucket() constructs (or rebuilds) the client for that bucket, and from then on every read and write goes through the S3 client. The active bucket is the R2 bucket the client targets.

Usage

The API is the same as every other storage backend.
Upload a local file or a whole directory, and stream large files instead of buffering them:

Custom buckets

Register a named storage under your own class so each bucket has a dedicated, injectable type:

Use in the app

In an @talosjs/app application, storage isn’t a dedicated App config slot. CloudflareStorage registers itself with the container through @decorator.storage() as soon as the class is imported, and you inject it wherever you read or write files.
Inject it into a service or controller, set the bucket, and use the IStorage methods:
To bind a dedicated R2 bucket to its own injectable type, register a subclass with @decorator.storage():
Set STORAGE_CLOUDFLARE_ACCESS_KEY, STORAGE_CLOUDFLARE_SECRET_KEY, STORAGE_CLOUDFLARE_ENDPOINT, and (optionally) STORAGE_CLOUDFLARE_REGION in your .env.yml (or environment) so the backend can connect.

Exceptions

CloudflareStorage throws StorageException on misconfiguration or a failed operation, carrying a machine-readable key.

Working with R2 day to day

Call setBucket() before any read or write, since that call also builds the S3 client for the bucket. Point STORAGE_CLOUDFLARE_ENDPOINT at your account’s R2 S3 endpoint rather than the public bucket URL. Group objects under stable prefixes like avatars/123.png to keep list() and clearBucket() predictable, and prefer getAsStream() and putFile/putDir over loading whole files into memory. Keep the access key, secret key, and endpoint in .env, never in source. When you want a bucket to be injectable on its own, register a CloudflareStorage subclass for it so it carries its own type. See the Storage component for the full interface and the other backends, including Bunny Storage.