How to use Cloudflare R2 in Base

Object storage behind an S3-compatible interface: declare a bucket once, reach the store by injection.

Cloudflare
AhraJuly 22, 2026

How to use Cloudflare R2 in Base

R2 is Cloudflare's object store: somewhere to keep files, blobs, uploads, anything you address by a key and read or write as a whole. Images a user sent you, a generated PDF, a backup archive. Base wraps R2 behind an S3-compatible object store interface, so the way you read and write objects looks the same as it would against any other S3-style store, and the way you reach the store looks like the rest of your Base worker.

The object store, not just R2

Base models object storage as an interface, not a vendor. The store you get exposes the operations you expect from an S3-style API: read an object's metadata, get an object, put one, delete one, and list what is in a bucket. R2 is the backing today, and the interface is shaped so other S3-compatible stores could sit behind it later without changing your code. You write against the store, not against R2 specifically, which is the same instinct Base applies everywhere: depend on the capability, not the provider.

Declaring a bucket

A bucket is declared once, at the worker root, in your object store settings. Each bucket entry names the bucket and the binding that connects it to the actual R2 bucket in your wrangler.toml. Buckets come in two shapes: a public bucket, which carries a domain map so its objects can be served from a hostname, and a private bucket, which has no public domain and is reachable only through your worker. The setting enforces the distinction, so a private bucket cannot accidentally grow a public face.

You declare the bucket in one place. Everything else is asking for it.

Reaching the store

Once a bucket is declared, you get a store bound to it by asking for one. Base injects an object store for the named bucket directly into the class that needs it, the same dependency-injection move you use for a database connection or a queue. You do not reach for the raw R2 binding and you do not thread it through your call stack by hand. You declare which bucket you want and the framework hands you a store wired to it.

With the store in hand, the operations are the plain object-store verbs: get an object by key, put data under a key, delete by key, head a key to read just its metadata, and list a prefix to browse what is there. The list operation supports prefix filtering and delimiter-based folder browsing, so you can walk objects as if they lived in folders even though the store underneath is flat.

When R2 is the right shelf

R2 is for whole objects you address by key: files, media, archives, anything you store and retrieve in one piece. It is not a database and not a cache. If you need to query by something other than a key, you want the ORM. If you need fast small reads of read-heavy values, you want KV. R2 is the shelf for the big things that travel as a unit, and Base makes putting them on that shelf a matter of declaring a bucket and asking for the store.

The mental model to keep

Object storage in Base is an S3-compatible interface backed by R2: declare your buckets once in settings, reach a bucket's store through injection, and use get, put, delete, head, and list by key. Keep public and private buckets distinct in the settings, and reach for R2 when your data is a whole object addressed by a key, not a row you query or a value you cache.

How to use Cloudflare R2 in Base • System