Pebellslearn by building
0%
RoadmapsMatrixCoursesSandboxSign in
R2 Storage and the Developer Toolchain  ›  Lesson

R2 — Buckets of Objects

Object Storage 15 minR2 stores objects — a key, a streamed body and metadata — in buckets, with strong consistency and no egress fees, and is the place for files rather than small lookup values.

R2 — Buckets of Objects

Hoplink users now want to shorten links to files: a PDF menu, a holiday photo, a slide deck. KV is the wrong home — it is tuned for small values read everywhere, not for megabytes of binary data. R2 is Cloudflare's object storage: it holds files of any size, speaks the same API as Amazon S3, and charges nothing for data going out (no egress fees), which matters when a popular file is downloaded thousands of times.

The rule

A bucket holds objects; an object is a key, a body and metadata.

  • Bucket — a named container, such as hop-files. You create it with wrangler r2 bucket create hop-files and bind it as FILES.
  • Key — the object's name, like priya/menu.pdf. There are no real folders; slashes are just characters that make keys look like paths.
  • Body — the bytes, handled as a stream.
  • Metadata — httpMetadata (content type, cache rules) and your own customMetadata.
// upload: stream the request body straight into R2
await env.FILES.put("priya/menu.pdf", request.body, {
  httpMetadata: { contentType: "application/pdf" },
  customMetadata: { owner: "priya" },
});

// download
const object = await env.FILES.get("priya/menu.pdf");
if (object === null) return new Response("Not found", { status: 404 });

const headers = new Headers();
object.writeHttpMetadata(headers);
headers.set("etag", object.httpEtag);
return new Response(object.body, { headers });

The other bucket operations:

  • head(key) — metadata only, no body. Cheap for "does it exist and how big is it?"
  • delete(key) — accepts one key or an array of keys.
  • list({ prefix: "priya/", cursor }) — returns objects, a truncated flag and a cursor for the next page. Add delimiter: "/" to get folder-like groupings.

Unlike KV, R2 is strongly consistent: once put finishes, every reader everywhere sees the new object.

The shortcut

To choose between KV and R2, ask two questions: How big? How is it used? Small values looked up by name on every request → KV. Files, anything large, anything a user downloads → R2. Often you use both: KV (or D1) maps the short code to an R2 key, and R2 holds the bytes.

Serving hop.example/menuStep 1 of 4
  1. Look up the code

    env.LINKS.get("menu") returns file:priya/menu.pdf — a pointer, not the file.

Trap: forgetting contentType on upload. The file is stored fine, but browsers then download a mystery blob instead of displaying the image or PDF.

Drills

Answer fast — trust the rule.

quick drill

What does env.FILES.get("missing.pdf") return when the object does not exist?

quick drill

You only need to know whether a file exists and its size. Which call is cheapest?

quick drill

Why is priya/menu.pdf not really inside a folder called priya?

quick drill

Right after a successful put, a reader on another continent requests the same key. What do they get?

Remember

  • A bucket holds objects; an object is a key, a body and metadata.
  • Shortcut: small lookups → KV; files and large blobs → R2; often both together.
  • Trap: uploading without a content type, so browsers cannot display the file.

Practice — level up

0/1 solved

Write your answer, then compare it with a rubric and a model answer.

ExplainServing a file link

Explain how Hoplink serves hop.example/menu when that code points to a PDF, naming the KV and R2 calls and what makes the browser display the PDF correctly.