Claude
Skills
Sign in
Back

r2-storage

Included with Lifetime
$97 forever

S3-compatible object storage for files, images, and large data. Load when handling file uploads, storing images/videos/documents, generating presigned URLs, using multipart uploads for large files, migrating from S3, or serving static assets from buckets.

General

What this skill does


# R2 Object Storage

Store and retrieve objects at scale using Cloudflare's S3-compatible object storage.

## When to Use

- File uploads (images, videos, documents)
- AI assets and model artifacts
- Image and media asset storage
- Structured data (JSON, CSV, logs)
- User-facing uploads and downloads
- Static asset hosting
- Backup and archival storage
- S3-compatible workflows with existing tools

## FIRST: Create R2 Bucket

```bash
# Create bucket
wrangler r2 bucket create my-bucket

# Create with location hint
wrangler r2 bucket create my-bucket --location wnam

# List buckets
wrangler r2 bucket list
```

## Quick Reference

| Operation | API |
|-----------|-----|
| Upload object | `await bucket.put(key, data, { httpMetadata })` |
| Download object | `const obj = await bucket.get(key); const data = await obj.text()` |
| Delete object | `await bucket.delete(key)` |
| List objects | `const list = await bucket.list({ prefix, limit })` |
| Get metadata | `const obj = await bucket.head(key)` |
| Multipart upload | `const upload = await bucket.createMultipartUpload(key)` |
| Generate signed URL | Use presigned URL patterns with R2's S3 compatibility |

## Wrangler Configuration

```jsonc
// wrangler.jsonc
{
  "name": "my-worker",
  "main": "src/index.ts",
  "compatibility_date": "2026-01-01",
  "r2_buckets": [
    {
      "binding": "BUCKET",
      "bucket_name": "my-bucket"
    }
  ]
}
```

**TypeScript Types** (run `wrangler types` to generate):

```typescript
export interface Env {
  BUCKET: R2Bucket;
}
```

## Basic Upload and Download

```typescript
import { R2Bucket } from "@cloudflare/workers-types";

export interface Env {
  BUCKET: R2Bucket;
}

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

    // Upload
    if (request.method === "PUT") {
      await env.BUCKET.put(key, request.body, {
        httpMetadata: {
          contentType: request.headers.get("content-type") || "application/octet-stream",
        },
      });
      return new Response("Uploaded", { status: 201 });
    }

    // Download
    if (request.method === "GET") {
      const object = await env.BUCKET.get(key);
      
      if (!object) {
        return new Response("Not found", { status: 404 });
      }

      return new Response(object.body, {
        headers: {
          "Content-Type": object.httpMetadata?.contentType || "application/octet-stream",
          "ETag": object.httpEtag,
          "Cache-Control": object.httpMetadata?.cacheControl || "public, max-age=3600",
        },
      });
    }

    // Delete
    if (request.method === "DELETE") {
      await env.BUCKET.delete(key);
      return new Response("Deleted", { status: 204 });
    }

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

## Multipart Form Upload Handler

Handle file uploads from HTML forms or multipart requests.

```typescript
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    if (request.method !== "POST") {
      return new Response("Method not allowed", { status: 405 });
    }

    const formData = await request.formData();
    const file = formData.get("file") as File;

    if (!file) {
      return new Response("No file provided", { status: 400 });
    }

    // Generate unique key
    const key = `uploads/${crypto.randomUUID()}-${file.name}`;

    // Upload to R2
    await env.BUCKET.put(key, file.stream(), {
      httpMetadata: {
        contentType: file.type,
      },
      customMetadata: {
        originalName: file.name,
        uploadedAt: new Date().toISOString(),
      },
    });

    return Response.json({
      success: true,
      key,
      url: `/files/${key}`,
    });
  },
};
```

## List Objects with Pagination

```typescript
async function listAllObjects(
  bucket: R2Bucket,
  prefix: string = ""
): Promise<R2Object[]> {
  const objects: R2Object[] = [];
  let cursor: string | undefined;

  do {
    const listed = await bucket.list({
      prefix,
      cursor,
      limit: 1000,
    });

    objects.push(...listed.objects);
    cursor = listed.truncated ? listed.cursor : undefined;
  } while (cursor);

  return objects;
}

// Usage
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);
    const prefix = url.searchParams.get("prefix") || "";

    const objects = await listAllObjects(env.BUCKET, prefix);

    return Response.json({
      count: objects.length,
      objects: objects.map((obj) => ({
        key: obj.key,
        size: obj.size,
        uploaded: obj.uploaded,
      })),
    });
  },
};
```

## Conditional Operations (ETags)

Use ETags for conditional reads/writes to prevent race conditions.

```typescript
// Conditional write (only if not modified)
const existingObject = await env.BUCKET.head("config.json");

if (existingObject) {
  // Update only if ETag matches
  await env.BUCKET.put("config.json", newData, {
    httpMetadata: {
      contentType: "application/json",
    },
    onlyIf: {
      etagMatches: existingObject.httpEtag,
    },
  });
}

// Conditional read (If-None-Match)
const object = await env.BUCKET.get("image.jpg", {
  onlyIf: {
    etagDoesNotMatch: cachedEtag,
  },
});

if (object === null) {
  // Object not modified - return 304
  return new Response(null, {
    status: 304,
    headers: { "ETag": cachedEtag },
  });
}
```

## Custom Metadata

Store application-specific metadata alongside objects.

```typescript
// Store with custom metadata
await env.BUCKET.put("document.pdf", pdfData, {
  httpMetadata: {
    contentType: "application/pdf",
  },
  customMetadata: {
    userId: "user-123",
    documentType: "invoice",
    version: "2",
    tags: "finance,2024",
  },
});

// Read metadata without downloading body
const object = await env.BUCKET.head("document.pdf");
console.log(object.customMetadata?.userId); // "user-123"
```

## Range Requests (Partial Downloads)

Efficiently download portions of large files.

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

    if (rangeHeader) {
      // Parse range: "bytes=0-1023"
      const match = rangeHeader.match(/bytes=(\d+)-(\d*)/);
      if (match) {
        const start = parseInt(match[1], 10);
        const end = match[2] ? parseInt(match[2], 10) : undefined;

        const object = await env.BUCKET.get(key, {
          range: { offset: start, length: end ? end - start + 1 : undefined },
        });

        if (!object) {
          return new Response("Not found", { status: 404 });
        }

        return new Response(object.body, {
          status: 206,
          headers: {
            "Content-Type": object.httpMetadata?.contentType || "application/octet-stream",
            "Content-Range": `bytes ${start}-${end || object.size - 1}/${object.size}`,
            "Content-Length": object.size.toString(),
          },
        });
      }
    }

    // Regular full download
    const object = await env.BUCKET.get(key);
    if (!object) {
      return new Response("Not found", { status: 404 });
    }

    return new Response(object.body, {
      headers: {
        "Content-Type": object.httpMetadata?.contentType || "application/octet-stream",
      },
    });
  },
};
```

## AWS SDK Integration (S3 Compatible)

R2 is fully compatible with the AWS S3 API. Use the official AWS SDK v3.

**Install dependencies:**

```bash
npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
```

**wrangler.jsonc** (add Node.js compatibility):

```jsonc
{
  "compatibility_flags": ["nodejs_compat_v2"],
  "r2_buckets": [
    { "binding": "BUCKET", "bucket_name": "my-bucket" }
  ]
}
```

**Using AWS SDK:**

```typescript
import { S3Client, PutObjectCommand, GetObjectCommand } from "@aws-sdk/client-s3";
imp
Files: 5
Size: 66.3 KB
Complexity: 47/100
Category: General

Related in General