$npx -y skills add jackspace/ClaudeSkillz --skill cloudflare-r2Complete knowledge domain for Cloudflare R2 - S3-compatible object storage on Cloudflare's edge network. Use when: creating R2 buckets, uploading files to R2, downloading objects, configuring R2 bindings, setting up CORS, generating presigned URLs, multipart uploads, storing imag
| 1 | # Cloudflare R2 Object Storage |
| 2 | |
| 3 | **Status**: Production Ready ✅ |
| 4 | **Last Updated**: 2025-10-21 |
| 5 | **Dependencies**: cloudflare-worker-base (for Worker setup) |
| 6 | **Latest Versions**: wrangler@4.43.0, @cloudflare/workers-types@4.20251014.0, aws4fetch@1.0.20 |
| 7 | |
| 8 | --- |
| 9 | |
| 10 | ## Quick Start (5 Minutes) |
| 11 | |
| 12 | ### 1. Create R2 Bucket |
| 13 | |
| 14 | ```bash |
| 15 | # Via Wrangler CLI (recommended) |
| 16 | npx wrangler r2 bucket create my-bucket |
| 17 | |
| 18 | # Or via Cloudflare Dashboard |
| 19 | # https://dash.cloudflare.com → R2 Object Storage → Create bucket |
| 20 | ``` |
| 21 | |
| 22 | **Bucket Naming Rules:** |
| 23 | - 3-63 characters |
| 24 | - Lowercase letters, numbers, hyphens only |
| 25 | - Must start/end with letter or number |
| 26 | - Globally unique within your account |
| 27 | |
| 28 | ### 2. Configure R2 Binding |
| 29 | |
| 30 | Add to your `wrangler.jsonc`: |
| 31 | |
| 32 | ```jsonc |
| 33 | { |
| 34 | "name": "my-worker", |
| 35 | "main": "src/index.ts", |
| 36 | "compatibility_date": "2025-10-11", |
| 37 | "r2_buckets": [ |
| 38 | { |
| 39 | "binding": "MY_BUCKET", // Available as env.MY_BUCKET in your Worker |
| 40 | "bucket_name": "my-bucket", // Name from wrangler r2 bucket create |
| 41 | "preview_bucket_name": "my-bucket-preview" // Optional: separate bucket for dev |
| 42 | } |
| 43 | ] |
| 44 | } |
| 45 | ``` |
| 46 | |
| 47 | **CRITICAL:** |
| 48 | - `binding` is how you access the bucket in code (`env.MY_BUCKET`) |
| 49 | - `bucket_name` is the actual R2 bucket name |
| 50 | - `preview_bucket_name` is optional but recommended for separate dev/prod data |
| 51 | |
| 52 | ### 3. Basic Upload/Download |
| 53 | |
| 54 | ```typescript |
| 55 | // src/index.ts |
| 56 | import { Hono } from 'hono'; |
| 57 | |
| 58 | type Bindings = { |
| 59 | MY_BUCKET: R2Bucket; |
| 60 | }; |
| 61 | |
| 62 | const app = new Hono<{ Bindings: Bindings }>(); |
| 63 | |
| 64 | // Upload file |
| 65 | app.put('/upload/:filename', async (c) => { |
| 66 | const filename = c.req.param('filename'); |
| 67 | const body = await c.req.arrayBuffer(); |
| 68 | |
| 69 | try { |
| 70 | const object = await c.env.MY_BUCKET.put(filename, body, { |
| 71 | httpMetadata: { |
| 72 | contentType: c.req.header('content-type') || 'application/octet-stream', |
| 73 | }, |
| 74 | }); |
| 75 | |
| 76 | return c.json({ |
| 77 | success: true, |
| 78 | key: object.key, |
| 79 | size: object.size, |
| 80 | etag: object.etag, |
| 81 | }); |
| 82 | } catch (error: any) { |
| 83 | console.error('R2 Upload Error:', error.message); |
| 84 | return c.json({ error: 'Upload failed' }, 500); |
| 85 | } |
| 86 | }); |
| 87 | |
| 88 | // Download file |
| 89 | app.get('/download/:filename', async (c) => { |
| 90 | const filename = c.req.param('filename'); |
| 91 | |
| 92 | try { |
| 93 | const object = await c.env.MY_BUCKET.get(filename); |
| 94 | |
| 95 | if (!object) { |
| 96 | return c.json({ error: 'File not found' }, 404); |
| 97 | } |
| 98 | |
| 99 | return new Response(object.body, { |
| 100 | headers: { |
| 101 | 'Content-Type': object.httpMetadata?.contentType || 'application/octet-stream', |
| 102 | 'ETag': object.httpEtag, |
| 103 | 'Cache-Control': object.httpMetadata?.cacheControl || 'public, max-age=3600', |
| 104 | }, |
| 105 | }); |
| 106 | } catch (error: any) { |
| 107 | console.error('R2 Download Error:', error.message); |
| 108 | return c.json({ error: 'Download failed' }, 500); |
| 109 | } |
| 110 | }); |
| 111 | |
| 112 | export default app; |
| 113 | ``` |
| 114 | |
| 115 | ### 4. Deploy and Test |
| 116 | |
| 117 | ```bash |
| 118 | # Deploy |
| 119 | npx wrangler deploy |
| 120 | |
| 121 | # Test upload |
| 122 | curl -X PUT https://my-worker.workers.dev/upload/test.txt \ |
| 123 | -H "Content-Type: text/plain" \ |
| 124 | -d "Hello, R2!" |
| 125 | |
| 126 | # Test download |
| 127 | curl https://my-worker.workers.dev/download/test.txt |
| 128 | ``` |
| 129 | |
| 130 | --- |
| 131 | |
| 132 | ## R2 Workers API |
| 133 | |
| 134 | ### Type Definitions |
| 135 | |
| 136 | ```typescript |
| 137 | // Add to env.d.ts or worker-configuration.d.ts |
| 138 | interface Env { |
| 139 | MY_BUCKET: R2Bucket; |
| 140 | // ... other bindings |
| 141 | } |
| 142 | |
| 143 | // For Hono |
| 144 | type Bindings = { |
| 145 | MY_BUCKET: R2Bucket; |
| 146 | }; |
| 147 | |
| 148 | const app = new Hono<{ Bindings: Bindings }>(); |
| 149 | ``` |
| 150 | |
| 151 | ### put() - Upload Objects |
| 152 | |
| 153 | **Signature:** |
| 154 | ```typescript |
| 155 | put(key: string, value: ReadableStream | ArrayBuffer | ArrayBufferView | string | Blob, options?: R2PutOptions): Promise<R2Object | null> |
| 156 | ``` |
| 157 | |
| 158 | **Basic Usage:** |
| 159 | |
| 160 | ```typescript |
| 161 | // Upload from request body |
| 162 | await env.MY_BUCKET.put('path/to/file.txt', request.body); |
| 163 | |
| 164 | // Upload string |
| 165 | await env.MY_BUCKET.put('config.json', JSON.stringify({ foo: 'bar' })); |
| 166 | |
| 167 | // Upload ArrayBuffer |
| 168 | await env.MY_BUCKET.put('image.png', await file.arrayBuffer()); |
| 169 | ``` |
| 170 | |
| 171 | **With Metadata:** |
| 172 | |
| 173 | ```typescript |
| 174 | const object = await env.MY_BUCKET.put('document.pdf', fileData, { |
| 175 | httpMetadata: { |
| 176 | contentType: 'application/pdf', |
| 177 | contentLanguage: 'en-US', |
| 178 | contentDisposition: 'attachment; filename="report.pdf"', |
| 179 | contentEncoding: 'gzip', |
| 180 | cacheCon |