CellB

R2

env.<BINDING> of an r2_buckets entry: get, put, head, delete, list, conditional requests and multipart uploads. Objects are not kept in a cell: like celld, CellB writes them directly to storage.

{
  "name": "r2",
  "main": "index.js",
  "compatibility_date": "2026-01-01",
  "r2_buckets": [
    { "binding": "FILES", "bucket_name": "example-files" }
  ]
}
export default {
  async fetch(request, env) {
    const key = new URL(request.url).pathname.slice(1);
    if (!key) return new Response("Use /KEY.", { status: 400 });

    if (request.method === "PUT") {
      await env.FILES.put(key, request.body, {
        httpMetadata: {
          contentType: request.headers.get("content-type") ??
            "application/octet-stream",
        },
      });
      return new Response(null, { status: 204 });
    }

    if (request.method === "DELETE") {
      await env.FILES.delete(key);
      return new Response(null, { status: 204 });
    }

    if (request.method === "GET") {
      const object = await env.FILES.get(key);
      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 });
    }

    return new Response("Method not allowed.", { status: 405 });
  },
};

This is celld’s r2 example (conformance/celld-examples/r2), copied unchanged and run by the conformance suite on Node and in Chromium.

Where objects live depends on the storage

StorageObjectMetadataAn open multipart upload
A fleet bucket (shared with celld)r2/<bucket>/<key>, celld’s layoutthe user-metadata entry celld-r2its parts under r2-uploads/<bucket>/<uploadId>/
The project’s FilesApi (no fleet bucket)objects/<encoded key>meta/<encoded key>.jsonmultipart/<uploadId>/

An object written by a single request has an MD5 ETag; a multipart object has none, as on celld. On a fleet bucket, objects written by put and by multipart upload cross between celld and CellB with their metadata.

How it is checked

celld’s r2 example and CellB’s r2-api, r2-advanced and r2-details projects, on Node and in Chromium; e2e/p11f-celld-one-bucket.test.ts moves objects both ways.

What breaks

  • A multipart upload opened on one runtime cannot be finished on the other. celld keeps an open upload in its node’s memory and answers R2 multipart upload <id> is not open on this node: …; CellB answers completeMultipartUpload: The specified multipart upload does not exist. (10024) for an upload celld opened. Complete an upload where it was opened; the finished object reads on both.
  • In celld dev, R2 ETags are a store generation number; CellB’s are the store’s ETag.
  • Errors carry celld’s messages: R2 <op> (binding <NAME>): ….