Skip to main content
R2 stores objects (files) by key, with HTTP metadata, custom metadata, range reads, and multipart uploads. Use it for uploads, images, exports, generated files, and backups. Files that ship with your code belong in static assets instead. celld, the Judt runtime, implements the Cloudflare R2 binding API. It has no presigned URLs, no public bucket URL, and no S3 endpoint, so your Worker handles every upload and download.

Configure the binding

To add a bucket, declare it in the wrangler.json file:
wrangler.json
The configuration follows these rules:
  • The binding and bucket_name keys are required.
  • You don’t provision the bucket. celld creates it on first use.
  • Deploy refuses every key other than binding and bucket_name, including jurisdiction and preview_bucket_name.
Don’t change the bucket_name value after data exists. A different name opens an empty bucket, and the objects stay under the old name.

All bytes go through the Worker

The Worker receives each upload and sends each download. This design has the following consequences:
  • Your Worker does all authorization. Before a download, confirm that the user owns the key.
  • You must stream all bodies. The isolate heap is 128 MB, so don’t read a large file into memory with await request.arrayBuffer().
  • For browser uploads, send the file as the raw request body, for example fetch(url, { method: "PUT", body: file }). The request.formData() method keeps the full file in memory.

Upload a file

The following handler streams the request body into R2. The json function is a small helper that returns a JSON response.
index.js
Follow these rules when you upload:
  • Pass request.body directly to the put method.
  • A streamed body has no known length. Check the size with the content-length header, or count the bytes in a TransformStream.
  • A streamed body of 8 MiB or more becomes a multipart upload automatically.
  • Generate keys on the server. Don’t use a key from the client.
  • Set httpMetadata.contentType on each put call. To make the browser download the file instead of displaying it, also set contentDisposition, for example 'attachment; filename="report.csv"'.

Download a file

The following handler serves an object, with support for range requests and conditional requests:
index.js

R2 API

The following table summarizes the methods of an R2 binding: An R2Object has the key, size, etag, httpEtag, uploaded, httpMetadata, customMetadata, and range properties. The version property is the same as the etag property. Conditional calls work.

Keys

Object keys follow these rules:
  • A key is 1 to 1,024 bytes long.
  • The namespace is flat. a/b, /a/b, a//b, and a/b/ are four different keys.
  • Use ASCII keys. The list method sorts keys that contain non-ASCII or special characters in a different order from Cloudflare.
  • To query files by owner, name, or date, keep an index in D1.
R2 on celld has no presigned URLs. To share a file, generate a random token, and store the mapping from the token to the object key in KV with an expiry. Then serve the file from a Worker route. The following example assumes a KV binding named LINKS next to the FILES bucket:
index.js

Differences from Cloudflare

celld differs from Cloudflare R2 in the following ways:
  • Presigned URLs, public buckets, the S3 API, event notifications, the ssecKey option, and lifecycle rules aren’t available.
  • Deploy refuses the jurisdiction and preview_bucket_name keys.
  • A multipart upload must finish on the node and process that started it.
  • celld refuses a conditional put of a streamed body over 8 MiB.
  • The list method sorts keys with non-ASCII or special characters in a different order.
  • The version property of an object is the same as its etag.