Skip to content
sproutboat
Esc
↑↓navigate↵open⌘Jpreview
On this page

R2-compatible object storage

Object storage bound to env.NAME, with the R2 binding API, multipart uploads and direct transfer URLs.

Object storage with the R2 binding API. Each object’s bytes live in their own file on disk.

Configuration

"r2_buckets": ["MEDIA"]

Bare names are auto-provisioned on deploy; see Storage resources.

Usage

env.MEDIA.put("path/file.txt", body, { httpMetadata: { contentType: "text/plain" } });
const obj = env.MEDIA.get("path/file.txt");   // null when missing
return obj.toResponse();                      // serves the bytes unchanged
env.MEDIA.head("path/file.txt");              // metadata only
env.MEDIA.delete("path/file.txt");
const { objects, truncated, cursor } = env.MEDIA.list({ prefix: "path/", limit: 100 });

From compatibility date 2026-10-26 R2 calls return Promises, as on Workers; before it they are synchronous. await works at both.

Methods

Method Signature Returns
put (key, body, { httpMetadata?, customMetadata? }) void
get (key) an object, or null
head (key) the object’s metadata without the body, or null
delete (key) void
list ({ prefix?, limit?, cursor? }) { objects, truncated, cursor }. limit defaults to and is capped at 1000.
createMultipartUpload (key, { httpMetadata?, customMetadata? }) a multipart upload
resumeMultipartUpload (key, uploadId) a multipart upload
createUploadUrl (key, { maxBytes?, expiresIn?, sha256?, httpMetadata?, customMetadata? }) { url, expiresAt }
createDownloadUrl (key, { expiresIn? }) { url, expiresAt }

An object has key, size, etag, httpEtag (quoted, for an ETag header), uploaded, httpMetadata and customMetadata. From get, it also has body, text(), json() and toResponse(init?). toResponse sets content-type from httpMetadata and etag from httpEtag unless init.headers sets them.

Large files

put and get hold the whole object in memory, and a request body is capped at 1 MiB by default. For anything larger, use one of these:

Direct transfer URLs. createUploadUrl and createDownloadUrl return a short-lived URL on your own hostname. The client sends the file straight to it with PUT, or fetches it with GET or HEAD (byte ranges supported), and the bytes never pass through your handler.

fetch(request) {
  const { url } = env.MEDIA.createUploadUrl("uploads/video.mp4", { maxBytes: 500 * 1024 * 1024 });
  return Response.json({ uploadTo: url });
}
  • Each URL works once and expires after expiresIn seconds: 900 by default, between 30 and 3600.
  • An upload is capped at maxBytes: 100 MiB by default, 5 GiB at most.
  • Pass sha256 (lowercase hex) to have the upload rejected when its contents do not match.

Multipart uploads. Send a large object in parts, each within the request body limit:

const upload = env.MEDIA.createMultipartUpload("big.bin");
const part = upload.uploadPart(1, chunk);           // part numbers 1 to 10000
upload.complete([part]);                            // or upload.abort()

Store upload.key and upload.uploadId to continue later with resumeMultipartUpload. Unfinished uploads are deleted after seven days.

Was this page helpful?