R2 storage
A Cloudflare R2 bucket with credentials scoped to it, and no egress fees.
AI/LLM: this page is available in plain markdown at /docs/resources/r2.md
npx extraorbital add r2✓ Provisioned r2/default (bucket cosmic-otter-default)
✓ Wrote 5 variables to .env
R2_BUCKET, R2_ENDPOINT, R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEYA project that mentions any R2_* variable gets this from
extraorbital provision without naming it.
R2 is S3-compatible, so every S3 client works against it unchanged. The reason to
pick it over s3 is bandwidth: reading your own data back
costs nothing, where S3 charges $0.09 per gigabyte.
What it writes
| Variable | Example |
|---|---|
R2_BUCKET | cosmic-otter-default |
R2_ENDPOINT | https://a1b2….r2.cloudflarestorage.com |
R2_ACCOUNT_ID | a1b2… |
R2_ACCESS_KEY_ID | f267e341f3dd4697bd3b9f71dd96247f |
R2_SECRET_ACCESS_KEY | 9c8e… |
The keys reach that bucket and no other, so an agent can never read another project's objects.
Options
npx extraorbital add r2 [--slug <name>] [--public] [--region <jurisdiction>]| Flag | Default | Description |
|---|---|---|
--slug | default | A second, fully separate bucket in the same project |
--public | off | Objects are publicly readable |
--region | default | An R2 jurisdiction: default, eu or fedramp |
R2 has no regions — it has jurisdictions, which decide where objects may
physically live. --region eu keeps them in the European Union. Anything that is
not one of those three is an error rather than a quiet fall back to default: if
you asked for eu because you have to, being handed an unrestricted bucket would
be worse than being told no.
A jurisdictional bucket answers only on its own endpoint, and R2_ENDPOINT is
written accordingly.
Options apply at creation. Re-running add with different options returns the
existing bucket unchanged — remove it first if you need a different one.
What it costs
Cloudflare's published rates, passed on unchanged, counted against your plan's allowance:
| Rate | |
|---|---|
| Storage | $0.015 / GB-month |
| Class A operations (writes, lists) | $4.50 / million |
| Class B operations (reads, heads) | $0.36 / million |
| Egress | free |
DeleteObject, DeleteBucket and AbortMultipartUpload are free, so cleaning up
after yourself costs nothing.
Usage appears in extraorbital ledger a day at a time, once each UTC day has
closed. A day still running is a number that keeps moving, so it is not billed
until it has stopped.
File retention on the Free plan
On the Free plan, files are deleted 30 days after they are uploaded. Cloudflare does this itself, as a bucket lifecycle rule; nothing of ours sweeps your bucket.
Paying lifts the rule, and the rule is lifted within the hour. But lifting it only
stops future deletions — it does not bring back files that have already
expired, because R2's expiry is an outright delete with nothing behind it. If
that matters to you, b2 keeps ten extra days in which an
upgrade can still recover the files.
Two details worth knowing:
- Expiry is approximate. Cloudflare removes objects typically within 24 hours of the deadline, so thirty days means thirty days and a bit.
- You cannot change this yourself. The credentials you are given reach objects, not bucket configuration, which is what makes the rule a rule.
This is separate from the 30-day idle rule that deletes an unused resource: a busy free bucket survives that one and still loses last month's files.
Use it
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
const r2 = new S3Client({
region: "auto",
endpoint: process.env.R2_ENDPOINT,
// R2 addresses a bucket as a path under the account host, not as a
// subdomain of it. Without this an SDK on its defaults finds nothing.
forcePathStyle: true,
credentials: {
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
},
});
await r2.send(
new PutObjectCommand({ Bucket: process.env.R2_BUCKET, Key: "report.json", Body: data }),
);Next
- B2 storage — cheaper per byte, if you read less than you store
- Quickstart — slugs, idempotency and sharing
- Pricing — plans, allowances and rates