R2 (object storage)
Object storage bound to env.NAME. Bytes live in their own file, never inline in a database row.
Configuration
"r2_buckets": ["MEDIA"]
Usage
env.MEDIA.put("path/file.txt", body, { httpMetadata: { contentType: "text/plain" } });
const obj = env.MEDIA.get("path/file.txt"); // obj.body, obj.size, obj.etag, obj.uploaded (or null)
obj.toResponse(); // serves obj.body byte for byte -- use this for
// binary content, not new Response(obj.body), which
// re-encodes it as text and corrupts anything non-ASCII
env.MEDIA.head("path/file.txt");
env.MEDIA.delete("path/file.txt");
const { objects, truncated, cursor } = env.MEDIA.list({ prefix: "path/", limit: 100 });
Methods
| Method | Signature | Returns |
|---|---|---|
put |
(key, body, { httpMetadata? }) |
— |
get |
(key) |
an object with .body, .size, .etag, .uploaded, and .toResponse() — or null |
head |
(key) |
metadata without the body |
delete |
(key) |
— |
list |
({ prefix?, limit?, cursor? }) |
{ objects, truncated, cursor } |
An object’s bytes live in their own file (r2-blobs/<hash-of-bucket-and-key>.blob next to the bucket’s metadata), never inline in a database row or the request frame — one write, one read, no extra copy on top. Binary objects round-trip byte for byte on both paths.
Memory and size limits
Two limits keep this bounded by default:
- Inbound request bodies over 1 MiB (
SB_REQUEST_BODY_MAX) are refused, so an upload cannot exceed that unless an operator raises it. - Outbound
fetch()refuses a response body over 32 MiB (SB_FETCH_MAX_BYTES).
True streaming put/get needs Porffor’s own ReadableStream support, which it does not have yet (porffor#349).