cloudflare-r2

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Cloudflare R2

Cloudflare R2

R2 is S3-compatible object storage with zero egress fees. Access via Workers Binding API or S3 API.

R2 是兼容S3的对象存储服务,无出口流量费用。可通过Workers Binding API或S3 API访问。

Quick Start

快速开始

bash
npx wrangler r2 bucket create my-bucket
jsonc
// wrangler.jsonc
{
  "r2_buckets": [{ "binding": "MY_BUCKET", "bucket_name": "my-bucket" }]
}
typescript
export interface Env {
  MY_BUCKET: R2Bucket;
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const key = new URL(request.url).pathname.slice(1);

    if (request.method === "PUT") {
      await env.MY_BUCKET.put(key, request.body);
      return new Response("Uploaded", { status: 201 });
    }

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

bash
npx wrangler r2 bucket create my-bucket
jsonc
// wrangler.jsonc
{
  "r2_buckets": [{ "binding": "MY_BUCKET", "bucket_name": "my-bucket" }]
}
typescript
export interface Env {
  MY_BUCKET: R2Bucket;
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const key = new URL(request.url).pathname.slice(1);

    if (request.method === "PUT") {
      await env.MY_BUCKET.put(key, request.body);
      return new Response("上传完成", { status: 201 });
    }

    if (request.method === "GET") {
      const object = await env.MY_BUCKET.get(key);
      if (!object) return new Response("未找到文件", { status: 404 });
      const headers = new Headers();
      object.writeHttpMetadata(headers);
      headers.set("etag", object.httpEtag);
      return new Response(object.body, { headers });
    }
    return new Response("不允许的请求方法", { status: 405 });
  },
};

Binding Configuration

绑定配置

jsonc
{
  "r2_buckets": [
    {
      "binding": "MY_BUCKET",
      "bucket_name": "my-bucket",
      "preview_bucket_name": "my-bucket-preview", // Optional: for local dev
      "jurisdiction": "eu" // Optional: GDPR
    }
  ]
}
See binding.md for details.

jsonc
{
  "r2_buckets": [
    {
      "binding": "MY_BUCKET",
      "bucket_name": "my-bucket",
      "preview_bucket_name": "my-bucket-preview", // 可选:用于本地开发
      "jurisdiction": "eu" // 可选:符合GDPR要求
    }
  ]
}
详情请参阅 binding.md

Workers Binding API (R2Bucket)

Workers Binding API (R2Bucket)

MethodDescription
head(key)
Get metadata without body
get(key, options?)
Get object with body
put(key, value, options?)
Store object
delete(key | keys[])
Delete up to 1000 keys
list(options?)
List objects with pagination
createMultipartUpload(key, options?)
Start multipart upload
resumeMultipartUpload(key, uploadId)
Resume multipart upload
方法描述
head(key)
获取元数据(不含文件内容)
get(key, options?)
获取对象(包含文件内容)
put(key, value, options?)
存储对象
delete(key | keys[])
删除最多1000个对象
list(options?)
分页列出对象
createMultipartUpload(key, options?)
启动分段上传
resumeMultipartUpload(key, uploadId)
恢复分段上传

get with range

按范围获取对象

typescript
const partial = await env.MY_BUCKET.get("large.bin", {
  range: { offset: 0, length: 1024 },
});
typescript
const partial = await env.MY_BUCKET.get("large.bin", {
  range: { offset: 0, length: 1024 },
});

put with metadata

带元数据存储对象

typescript
await env.MY_BUCKET.put("file.txt", "Hello!", {
  httpMetadata: { contentType: "text/plain" },
  customMetadata: { author: "Alice" },
  storageClass: "InfrequentAccess", // Optional
});
typescript
await env.MY_BUCKET.put("file.txt", "Hello!", {
  httpMetadata: { contentType: "text/plain" },
  customMetadata: { author: "Alice" },
  storageClass: "InfrequentAccess", // 可选
});

list with pagination

分页列出对象

typescript
const listed = await env.MY_BUCKET.list({ prefix: "images/", limit: 100 });
for (const obj of listed.objects) console.log(obj.key, obj.size);
if (listed.truncated) {
  /* use listed.cursor for next page */
}
See api.md for complete reference.

typescript
const listed = await env.MY_BUCKET.list({ prefix: "images/", limit: 100 });
for (const obj of listed.objects) console.log(obj.key, obj.size);
if (listed.truncated) {
  /* 使用listed.cursor获取下一页 */
}
完整参考请参阅 api.md

R2Object / R2ObjectBody

R2Object / R2ObjectBody

typescript
interface R2Object {
  key: string;
  version: string;
  size: number;
  etag: string;
  httpEtag: string;
  uploaded: Date;
  httpMetadata: R2HTTPMetadata;
  customMetadata: Record<string, string>;
  storageClass: "Standard" | "InfrequentAccess";
  writeHttpMetadata(headers: Headers): void;
}

interface R2ObjectBody extends R2Object {
  body: ReadableStream;
  arrayBuffer(): Promise<ArrayBuffer>;
  text(): Promise<string>;
  json<T>(): Promise<T>;
  blob(): Promise<Blob>;
}

typescript
interface R2Object {
  key: string;
  version: string;
  size: number;
  etag: string;
  httpEtag: string;
  uploaded: Date;
  httpMetadata: R2HTTPMetadata;
  customMetadata: Record<string, string>;
  storageClass: "Standard" | "InfrequentAccess";
  writeHttpMetadata(headers: Headers): void;
}

interface R2ObjectBody extends R2Object {
  body: ReadableStream;
  arrayBuffer(): Promise<ArrayBuffer>;
  text(): Promise<string>;
  json<T>(): Promise<T>;
  blob(): Promise<Blob>;
}

S3 API Compatibility

S3 API 兼容性

Endpoint:
https://<ACCOUNT_ID>.r2.cloudflarestorage.com
typescript
import { S3Client } from "@aws-sdk/client-s3";

const S3 = new S3Client({
  region: "auto",
  endpoint: `https://${ACCOUNT_ID}.r2.cloudflarestorage.com`,
  credentials: { accessKeyId: ACCESS_KEY_ID, secretAccessKey: SECRET_ACCESS_KEY },
});
Region: Always
"auto"
(or
"us-east-1"
alias).

端点:
https://<ACCOUNT_ID>.r2.cloudflarestorage.com
typescript
import { S3Client } from "@aws-sdk/client-s3";

const S3 = new S3Client({
  region: "auto",
  endpoint: `https://${ACCOUNT_ID}.r2.cloudflarestorage.com`,
  credentials: { accessKeyId: ACCESS_KEY_ID, secretAccessKey: SECRET_ACCESS_KEY },
});
区域:始终为
"auto"
(或别名
"us-east-1"
)。

Presigned URLs

预签名URL

typescript
import { GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

const downloadUrl = await getSignedUrl(S3, new GetObjectCommand({ Bucket: "my-bucket", Key: "file.pdf" }), { expiresIn: 3600 });

const uploadUrl = await getSignedUrl(S3, new PutObjectCommand({ Bucket: "my-bucket", Key: "file.pdf", ContentType: "application/pdf" }), { expiresIn: 3600 });
Important: Presigned URLs work only with S3 endpoint (not custom domains). Configure CORS for browser use. See presigned-urls.md.

typescript
import { GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

const downloadUrl = await getSignedUrl(S3, new GetObjectCommand({ Bucket: "my-bucket", Key: "file.pdf" }), { expiresIn: 3600 });

const uploadUrl = await getSignedUrl(S3, new PutObjectCommand({ Bucket: "my-bucket", Key: "file.pdf", ContentType: "application/pdf" }), { expiresIn: 3600 });
重要提示:预签名URL仅适用于S3端点(不适用于自定义域名)。若要在浏览器中使用,请配置CORS。详情请参阅 presigned-urls.md

Multipart Uploads

分段上传

For objects > 100 MB.
typescript
const mpu = await env.MY_BUCKET.createMultipartUpload("large.zip");
const part1 = await mpu.uploadPart(1, chunk1); // Min 5 MiB
const part2 = await mpu.uploadPart(2, chunk2);
await mpu.complete([part1, part2]);
// or: await mpu.abort();
Resume:
env.MY_BUCKET.resumeMultipartUpload(key, uploadId)
Limits: 5 MiB–5 GiB per part, 10,000 parts max, ~5 TiB max object. See multipart.md.

适用于大于100MB的对象。
typescript
const mpu = await env.MY_BUCKET.createMultipartUpload("large.zip");
const part1 = await mpu.uploadPart(1, chunk1); // 最小5 MiB
const part2 = await mpu.uploadPart(2, chunk2);
await mpu.complete([part1, part2]);
// 或:await mpu.abort();
恢复上传:
env.MY_BUCKET.resumeMultipartUpload(key, uploadId)
限制:每个分段大小为5 MiB–5 GiB,最多支持10,000个分段,单个对象最大约5 TiB。详情请参阅 multipart.md

CORS Configuration

CORS 配置

json
[
  {
    "AllowedOrigins": ["https://example.com"],
    "AllowedMethods": ["GET", "PUT"],
    "AllowedHeaders": ["Content-Type"],
    "ExposeHeaders": ["ETag"],
    "MaxAgeSeconds": 3600
  }
]
bash
npx wrangler r2 bucket cors set my-bucket --file cors.json

json
[
  {
    "AllowedOrigins": ["https://example.com"],
    "AllowedMethods": ["GET", "PUT"],
    "AllowedHeaders": ["Content-Type"],
    "ExposeHeaders": ["ETag"],
    "MaxAgeSeconds": 3600
  }
]
bash
npx wrangler r2 bucket cors set my-bucket --file cors.json

Lifecycle Policies

生命周期策略

bash
undefined
bash
undefined

Delete after 90 days

90天后删除对象

npx wrangler r2 bucket lifecycle add my-bucket --id "cleanup" --expire-days 90
npx wrangler r2 bucket lifecycle add my-bucket --id "cleanup" --expire-days 90

Transition to Infrequent Access

30天后转换为低频访问存储类别

npx wrangler r2 bucket lifecycle add my-bucket --id "archive" --transition-days 30 --transition-class STANDARD_IA
npx wrangler r2 bucket lifecycle add my-bucket --id "archive" --transition-days 30 --transition-class STANDARD_IA

With prefix

针对特定前缀

npx wrangler r2 bucket lifecycle add my-bucket --id "logs" --prefix "logs/" --expire-days 7
npx wrangler r2 bucket lifecycle add my-bucket --id "logs" --prefix "logs/" --expire-days 7

Abort incomplete multipart

中止未完成的分段上传

npx wrangler r2 bucket lifecycle add my-bucket --id "mpu" --abort-incomplete-days 1

See [lifecycle.md](references/lifecycle.md) for S3 API examples.

---
npx wrangler r2 bucket lifecycle add my-bucket --id "mpu" --abort-incomplete-days 1

S3 API示例请参阅 [lifecycle.md](references/lifecycle.md)。

---

Storage Classes

存储类别

ClassStorageNotes
Standard$0.015/GBDefault
InfrequentAccess$0.01/GB+$0.01/GB retrieval, 30-day min
typescript
await env.MY_BUCKET.put("archive.zip", data, { storageClass: "InfrequentAccess" });

类别存储费用说明
Standard$0.015/GB默认类别
InfrequentAccess$0.01/GB额外收取$0.01/GB的取回费用,最短存储期限30天
typescript
await env.MY_BUCKET.put("archive.zip", data, { storageClass: "InfrequentAccess" });

Event Notifications

事件通知

Push to Cloudflare Queues on object changes.
bash
npx wrangler queues create r2-events
npx wrangler r2 bucket notification create my-bucket --event-type object-create --queue r2-events
Events:
object-create
,
object-delete
typescript
export default {
  async queue(batch: MessageBatch<R2EventMessage>, env: Env) {
    for (const msg of batch.messages) {
      console.log(`${msg.body.action}: ${msg.body.object.key}`);
      msg.ack();
    }
  },
};

当对象发生变化时,推送事件至Cloudflare Queues。
bash
npx wrangler queues create r2-events
npx wrangler r2 bucket notification create my-bucket --event-type object-create --queue r2-events
支持的事件类型:
object-create
object-delete
typescript
export default {
  async queue(batch: MessageBatch<R2EventMessage>, env: Env) {
    for (const msg of batch.messages) {
      console.log(`${msg.body.action}: ${msg.body.object.key}`);
      msg.ack();
    }
  },
};

Public Buckets

公共存储桶

Custom domain (recommended)

自定义域名(推荐)

Dashboard → Bucket → Settings → Custom Domains → Add. Enables Cache, WAF, Access.
控制台 → 存储桶 → 设置 → 自定义域名 → 添加。可启用缓存、WAF、访问控制。

r2.dev (dev only)

r2.dev(仅用于开发)

Dashboard → Bucket → Settings → Public Development URL → Enable.
Warning: r2.dev is rate-limited. Use custom domain for production.

控制台 → 存储桶 → 设置 → 公共开发URL → 启用。
警告:r2.dev有请求速率限制。生产环境请使用自定义域名。

Data Migration

数据迁移

Super Slurper (bulk)

Super Slurper(批量迁移)

Dashboard → R2 → Data Migration. Copies from S3/GCS/compatible storage. Objects > 1 TB skipped.
控制台 → R2 → 数据迁移。从S3/GCS或兼容存储复制对象。大于1TB的对象将被跳过。

Sippy (incremental)

Sippy(增量迁移)

bash
npx wrangler r2 bucket sippy enable my-bucket --provider s3 --bucket source --access-key-id <KEY> --secret-access-key <SECRET>
Copies on-demand as objects are requested.

bash
npx wrangler r2 bucket sippy enable my-bucket --provider s3 --bucket source --access-key-id <KEY> --secret-access-key <SECRET>
当对象被请求时,按需复制。

Wrangler Commands

Wrangler 命令

bash
undefined
bash
undefined

Bucket

存储桶操作

wrangler r2 bucket create|delete|list|info <name>
wrangler r2 bucket create|delete|list|info <name>

Object

对象操作

wrangler r2 object put|get|delete <bucket>/<key> [--file <path>]
wrangler r2 object put|get|delete <bucket>/<key> [--file <path>]

CORS

CORS配置

wrangler r2 bucket cors set|get|delete <bucket>
wrangler r2 bucket cors set|get|delete <bucket>

Lifecycle

生命周期策略

wrangler r2 bucket lifecycle list|add|remove <bucket>
wrangler r2 bucket lifecycle list|add|remove <bucket>

Notifications

事件通知

wrangler r2 bucket notification list|create|delete <bucket>
wrangler r2 bucket notification list|create|delete <bucket>

Sippy

Sippy迁移

wrangler r2 bucket sippy enable|disable|get <bucket>

---
wrangler r2 bucket sippy enable|disable|get <bucket>

---

Limits

限制

ParameterLimit
Buckets per account1,000,000
Object size5 TiB
Single upload5 GiB
Multipart parts10,000
Key length1,024 bytes
Metadata size8,192 bytes
Delete batch1,000 keys
Lifecycle rules1,000/bucket
Notification rules100/bucket

参数限制值
每个账户的存储桶数量1,000,000
对象大小5 TiB
单次上传大小5 GiB
分段上传的分段数10,000
对象键长度1,024 字节
元数据大小8,192 字节
批量删除的对象数1,000 个
每个存储桶的生命周期规则数1,000条/bucket
每个存储桶的通知规则数100条/bucket

Pricing

定价

MetricStandardInfrequent Access
Storage$0.015/GB$0.01/GB
Class A$4.50/M$9.00/M
Class B$0.36/M$0.90/M
RetrievalFree$0.01/GB
EgressFreeFree
Free tier: 10 GB storage, 1M Class A, 10M Class B.
Class A: PUT, COPY, LIST, CreateMultipartUpload Class B: GET, HEAD Free: DELETE, AbortMultipartUpload
See pricing.md for optimization tips.

指标StandardInfrequent Access
存储费用$0.015/GB$0.01/GB
A类操作$4.50/百万次$9.00/百万次
B类操作$0.36/百万次$0.90/百万次
取回费用免费$0.01/GB
出口流量费用免费免费
免费额度:10 GB存储容量,100万次A类操作,1000万次B类操作。
A类操作:PUT、COPY、LIST、CreateMultipartUpload B类操作:GET、HEAD 免费操作:DELETE、AbortMultipartUpload
成本优化技巧请参阅 pricing.md

Conditional Operations

条件操作

typescript
const object = await env.MY_BUCKET.get("file.txt", {
  onlyIf: { etagMatches: expectedEtag },
  // or: etagDoesNotMatch, uploadedBefore, uploadedAfter
});

// Or from HTTP headers
const object = await env.MY_BUCKET.get("file.txt", { onlyIf: request.headers });

typescript
const object = await env.MY_BUCKET.get("file.txt", {
  onlyIf: { etagMatches: expectedEtag },
  // 或:etagDoesNotMatch, uploadedBefore, uploadedAfter
});

// 或从HTTP头获取条件
const object = await env.MY_BUCKET.get("file.txt", { onlyIf: request.headers });

Server-Side Encryption (SSE-C)

服务器端加密(SSE-C)

typescript
await env.MY_BUCKET.put("secret.txt", data, { ssecKey: encryptionKey });
const object = await env.MY_BUCKET.get("secret.txt", { ssecKey: encryptionKey });
Lost key = lost data.

typescript
await env.MY_BUCKET.put("secret.txt", data, { ssecKey: encryptionKey });
const object = await env.MY_BUCKET.get("secret.txt", { ssecKey: encryptionKey });
密钥丢失将导致数据无法恢复。

Prohibitions

注意事项

  • ❌ Do not use r2.dev for production
  • ❌ Do not store encryption keys in code
  • ❌ Do not skip CORS for browser access
  • ❌ Do not ignore multipart limits (5 MiB min)
  • ❌ Do not use presigned URLs with custom domains

  • ❌ 请勿在生产环境中使用r2.dev
  • ❌ 请勿在代码中存储加密密钥
  • ❌ 浏览器访问时请勿跳过CORS配置
  • ❌ 请勿忽略分段上传的限制(最小5 MiB)
  • ❌ 请勿在自定义域名中使用预签名URL

References

参考文档

  • binding.md — Binding configuration
  • api.md — Workers API reference
  • presigned-urls.md — Browser integration
  • multipart.md — Large file uploads
  • lifecycle.md — Object expiration
  • pricing.md — Cost optimization
  • binding.md — 绑定配置
  • api.md — Workers API参考
  • presigned-urls.md — 浏览器集成
  • multipart.md — 大文件上传
  • lifecycle.md — 对象过期策略
  • pricing.md — 成本优化

Cross-References

交叉参考

  • cloudflare-workers — Worker development
  • cloudflare-pages — Pages with R2
  • cloudflare-queues — Event consumers
  • cloudflare-d1 — SQL database
  • cloudflare-workers — Worker开发
  • cloudflare-pages — 结合R2的Pages服务
  • cloudflare-queues — 事件消费者
  • cloudflare-d1 — SQL数据库