This site is not affiliated with or endorsed by Cloudflare, Inc. It showcases deployable experiments across most Cloudflare products.

Blog20 min read

Object storage on Cloudflare R2: list, get, put, delete

Deep guide to Cloudflare R2 on Workers - bindings, CRUD APIs, presigned uploads, lifecycle, caching, SQL-over-R2, and the R2 Storage experiment.

Rows of illuminated servers in a data center aisle
Photo by Panumas Nikhomkhai on Unsplash

Cloudflare R2 is S3-compatible object storage that pairs naturally with Workers: store blobs next to your compute and avoid classic egress fees when Workers read the bucket. Typical uses include user uploads, generated screenshots, model artifacts, and caches for heavier experiment outputs.

This guide covers the CRUD mental model, binding setup, auth, lifecycle, adjacent products (KV, D1, Cache API, R2 SQL), and the R2 Storage experiment.

Cloudflare products in this pattern

ProductRoleDocs
R2Object storage bucketsProduct home
R2 Workers APIput / get / list / delete from WorkersBinding API
WorkersHTTP facade + validationRuntime
Workers bindingsr2_buckets in wranglerConfig
Presigned URLsBrowser-direct uploadsS3 API
R2 lifecycleExpire temp objectsLifecycle
D1Structured metadata about objectsSQL
KVTiny pointers / hot flagsKey-value
Cache APIShort-TTL HTTP cache in front of getsCache

When R2 is the right store

NeedPreferWhy
Files, images, PDFs, archivesR2Object semantics + size
Structured rows / SQLD1 (playground)Queries, joins
Small string configsKV (KV Notes)Ultra-simple get/put
Strongly consistent per-entity stateDurable ObjectsSingle-threaded entity

Do not force multi-MB blobs into KV. Do not force ad-hoc JSON files into R2 when D1 fits.

Worker + R2 binding pattern

Declare the bucket in wrangler.json (see R2 bindings), type it in env.d.ts, then call the binding:

await env.BUCKET.put(key, body, {
  httpMetadata: { contentType: "image/png" },
});

const object = await env.BUCKET.get(key);
if (object === null) {
  // 404 with stable code
}

Official reference: Workers API reference. The R2 Storage experiment wraps these calls behind HTTP so you can explore keys from curl before folding helpers into your app.

Suggested HTTP surface

MethodPathAction
PUT/objects/:keyUpload body
GET/objects/:keyDownload object
DELETE/objects/:keyDelete
GET/objectsList with prefix/cursor

Validate keys (no path traversal, length limits, charset). Return 404 with a stable code when missing. Stream large bodies when possible instead of buffering entire objects in memory.

Presigned uploads for browsers

For large browser uploads, prefer direct-to-R2 with presigned URLs so the Worker does not proxy gigabytes. The Presigned R2 Upload experiment shows that flow.

Pattern:

  1. Authenticated Worker mints a short-lived upload URL
  2. Browser PUTs the file to R2
  3. Worker records metadata in D1 if needed

Production hardening checklist

  1. Auth - never leave public put/delete open
  2. Content-Type - persist httpMetadata on put (Workers API)
  3. Size limits - reject oversized bodies early in the Worker
  4. Prefixes - namespace per tenant (tenantId/…)
  5. Lifecycle - object lifecycles for temp objects
  6. SQL over objects - analytics-style queries via R2 SQL Query when applicable
  7. Hot reads - optional Cache API in front (Edge Cache)

Pair R2 with Browser Rendering or Workers AI: generate a screenshot or audio file, then put for reuse.

S3 compatibility notes

R2’s S3-compatible API matters when you use existing SDKs, rclone, or tooling outside Workers. Inside Workers, prefer the native binding for lower friction. Know which path you are on so credentials and endpoints stay correct.

Deploy the experiment

  1. Create an R2 bucket (get started)
  2. Deploy R2 Storage with the bucket binding
  3. Exercise put → get → list → delete
  4. Add Presigned R2 Upload if browsers upload large files
  5. Copy src/lib helpers into your product Worker

You leave with a tested binding usage pattern and clear error responses - the boring foundation most edge apps need before fancier storage features.

Production checklist

  1. Create buckets per environment (dev/staging/prod).
  2. Namespace keys by tenant: tenantId/objectId.
  3. Never expose anonymous put/delete.
  4. Prefer presigned URLs for large browser uploads.
  5. Set lifecycle rules for temp objects.
  6. Store relational metadata in D1, not only object keys.
  7. Decide CORS carefully when browsing directly to R2.

FAQ

R2 vs KV for files?

KV is for small values with eventual consistency. Multi-MB files belong in R2.

Do Workers pay egress to read R2?

A major R2 value proposition is no egress fees for many common Cloudflare data-transfer paths - confirm current pricing on the R2 pricing page before budgeting.

Related experiments

These deployable references implement the patterns in this article. Open the docs for API details, then Deploy or clone the source.

  • R2 Storage API - Upload and serve files with Cloudflare R2 - private and public bucket patterns, object CRUD, and edge file storage reference
  • D1 SQL Playground - Read-only SQL playground over a seeded Cloudflare D1 database with safe SELECT validation - learn D1 at the edge
  • KV Notes - Simple note CRUD with Workers KV at the edge - minimal key-value storage reference for Cloudflare Workers
  • R2 SQL Query - Query Apache Iceberg tables via the Cloudflare R2 SQL HTTP API - analytics SQL over object storage from a Worker
  • Presigned R2 Upload - Generate presigned PUT URLs for direct browser-to-R2 uploads with aws4fetch - secure client uploads without Worker proxying
  • Edge Cache - Fetch and cache URLs with the Workers Cache API at the edge - Cache API reference pattern for Cloudflare Workers

Cloudflare Experiments is an independent open-source catalog - not affiliated with Cloudflare, Inc.