# R2 Configuration

## Workers Binding

**wrangler.jsonc:**

```jsonc
{
  "r2_buckets": [
    {
      "binding": "MY_BUCKET",
      "bucket_name": "my-bucket-name",
    },
  ],
}
```

## TypeScript Types

```typescript
interface Env {
  MY_BUCKET: R2Bucket
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const object = await env.MY_BUCKET.get('file.txt')
    return new Response(object?.body)
  },
}
```

## S3 SDK Setup

```typescript
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'

const s3 = new S3Client({
  region: 'auto',
  endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
  credentials: {
    accessKeyId: env.R2_ACCESS_KEY_ID,
    secretAccessKey: env.R2_SECRET_ACCESS_KEY,
  },
})

await s3.send(
  new PutObjectCommand({
    Bucket: 'my-bucket',
    Key: 'file.txt',
    Body: data,
    StorageClass: 'STANDARD', // or 'STANDARD_IA'
  })
)
```

## Location Hints

```bash
wrangler r2 bucket create my-bucket --location=enam

# Hints: wnam, enam, weur, eeur, apac, oc
# Jurisdictions (override hint): --jurisdiction=eu (or fedramp)
```

## CORS Configuration

CORS must be configured via S3 SDK or dashboard (not available in Workers API):

```typescript
import { S3Client, PutBucketCorsCommand } from '@aws-sdk/client-s3'

const s3 = new S3Client({
  region: 'auto',
  endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
  credentials: {
    accessKeyId: env.R2_ACCESS_KEY_ID,
    secretAccessKey: env.R2_SECRET_ACCESS_KEY,
  },
})

await s3.send(
  new PutBucketCorsCommand({
    Bucket: 'my-bucket',
    CORSConfiguration: {
      CORSRules: [
        {
          AllowedOrigins: ['https://example.com'],
          AllowedMethods: ['GET', 'PUT', 'HEAD'],
          AllowedHeaders: ['*'],
          ExposeHeaders: ['ETag'],
          MaxAgeSeconds: 3600,
        },
      ],
    },
  })
)
```

## Object Lifecycles

```typescript
import { PutBucketLifecycleConfigurationCommand } from '@aws-sdk/client-s3'

await s3.send(
  new PutBucketLifecycleConfigurationCommand({
    Bucket: 'my-bucket',
    LifecycleConfiguration: {
      Rules: [
        {
          ID: 'expire-old-logs',
          Status: 'Enabled',
          Prefix: 'logs/',
          Expiration: { Days: 90 },
        },
        {
          ID: 'transition-to-ia',
          Status: 'Enabled',
          Prefix: 'archives/',
          Transitions: [{ Days: 30, StorageClass: 'STANDARD_IA' }],
        },
      ],
    },
  })
)
```

## API Token Scopes

When creating R2 tokens, set minimal permissions:

| Permission          | Use Case                            |
| ------------------- | ----------------------------------- |
| Object Read         | Public serving, downloads           |
| Object Write        | Uploads only                        |
| Object Read & Write | Full object operations              |
| Admin Read & Write  | Bucket management, CORS, lifecycles |

**Best practice:** Separate tokens for Workers (read/write) vs admin tasks (CORS, lifecycles).

## Event Notifications

```jsonc
// wrangler.jsonc
{
  "r2_buckets": [
    {
      "binding": "MY_BUCKET",
      "bucket_name": "my-bucket",
      "event_notifications": [
        {
          "queue": "r2-events",
          "actions": ["PutObject", "DeleteObject", "CompleteMultipartUpload"],
        },
      ],
    },
  ],
  "queues": {
    "producers": [{ "binding": "R2_EVENTS", "queue": "r2-events" }],
    "consumers": [{ "queue": "r2-events", "max_batch_size": 10 }],
  },
}
```

## Bucket Management

```bash
wrangler r2 bucket create my-bucket --location=enam --storage-class=Standard
wrangler r2 bucket list
wrangler r2 bucket info my-bucket
wrangler r2 bucket delete my-bucket  # Must be empty
wrangler r2 bucket update-storage-class my-bucket --storage-class=InfrequentAccess

# Public bucket via dashboard
wrangler r2 bucket domain add my-bucket --domain=files.example.com
```
