askill
cloudflare-r2

cloudflare-r2Safety 90Repository

Cloudflare R2 object storage - upload, download, list, delete files, presigned URLs. Trigger: When working with R2 storage, file uploads, object storage, presigned URLs.

0 stars
1.2k downloads
Updated 2/5/2026

Package Files

Loading files...
SKILL.md

Critical Patterns

Setup Binding

# wrangler.toml
[[r2_buckets]]
binding = "MY_BUCKET"
bucket_name = "my-bucket"
export interface Env {
  MY_BUCKET: R2Bucket
}

Upload

// Upload file
await env.MY_BUCKET.put("path/to/file.txt", fileBuffer, {
  httpMetadata: {
    contentType: "text/plain",
    cacheControl: "max-age=31536000"
  },
  customMetadata: {
    uploadedBy: "user-123",
    originalName: "document.txt"
  }
})

// From FormData
app.post("/upload", async (c) => {
  const formData = await c.req.formData()
  const file = formData.get("file") as File
  
  if (!file) {
    return c.json({ error: "No file" }, 400)
  }
  
  const key = `uploads/${crypto.randomUUID()}-${file.name}`
  await c.env.MY_BUCKET.put(key, await file.arrayBuffer(), {
    httpMetadata: { contentType: file.type }
  })
  
  return c.json({ success: true, key })
})

Download

// Get object
const object = await env.MY_BUCKET.get("path/to/file.txt")

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

// Stream to response (efficient for large files)
return new Response(object.body, {
  headers: {
    "Content-Type": object.httpMetadata.contentType,
    "ETag": object.etag,
    "Cache-Control": "max-age=3600"
  }
})

// Access metadata
console.log(object.key)              // File key
console.log(object.size)             // Size in bytes
console.log(object.uploaded)         // Upload date
console.log(object.customMetadata)   // Custom metadata

List Objects

// List with pagination
const listed = await env.MY_BUCKET.list({
  limit: 100,                    // Max 1000
  prefix: "images/",             // Filter by prefix
  cursor: cursorToken,           // For pagination
  include: ["customMetadata"]    // Include metadata
})

for (const obj of listed.objects) {
  console.log(obj.key, obj.size, obj.uploaded)
}

// Next page
if (listed.truncated) {
  const nextPage = await env.MY_BUCKET.list({
    cursor: listed.cursor
  })
}

Delete

// Delete single
await env.MY_BUCKET.delete("path/to/file.txt")

// Delete multiple
await env.MY_BUCKET.delete([
  "file1.txt",
  "file2.txt",
  "images/photo.jpg"
])

Check Existence (Head)

// Get metadata without downloading body
const object = await env.MY_BUCKET.head("file.txt")

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

console.log(object.size)
console.log(object.etag)
// Note: object.body is undefined for head()

Presigned URLs

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

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

// Download URL (valid 1 hour)
const downloadUrl = await getSignedUrl(
  S3,
  new GetObjectCommand({
    Bucket: "my-bucket",
    Key: "file.txt"
  }),
  { expiresIn: 3600 }
)

// Upload URL
const uploadUrl = await getSignedUrl(
  S3,
  new PutObjectCommand({
    Bucket: "my-bucket",
    Key: "upload.txt",
    ContentType: "text/plain"
  }),
  { expiresIn: 3600 }
)

// Client can PUT/GET to these URLs

Performance Tips

// ✅ Stream large files (don't load into memory)
const object = await env.MY_BUCKET.get("large-file.mp4")
return new Response(object.body, {
  headers: { "Content-Type": "video/mp4" }
})

// ✅ Use head() for existence checks (no body transfer)
const exists = await env.MY_BUCKET.head("file.txt") !== null

// ❌ Wasteful - downloads entire file
const exists = await env.MY_BUCKET.get("file.txt") !== null

// ✅ Set cache headers for static files
await env.MY_BUCKET.put("logo.png", file, {
  httpMetadata: {
    contentType: "image/png",
    cacheControl: "public, max-age=31536000, immutable"
  }
})

Common Patterns

File Download Endpoint

app.get("/files/:key", async (c) => {
  const key = c.req.param("key")
  const object = await c.env.MY_BUCKET.get(key)
  
  if (!object) {
    return c.json({ error: "Not found" }, 404)
  }
  
  return new Response(object.body, {
    headers: {
      "Content-Type": object.httpMetadata.contentType,
      "Content-Disposition": `attachment; filename="${object.customMetadata?.originalName}"`,
      "ETag": object.etag
    }
  })
})

List Files with Pagination

app.get("/files", async (c) => {
  const cursor = c.req.query("cursor")
  const prefix = c.req.query("prefix")
  
  const listed = await c.env.MY_BUCKET.list({
    limit: 100,
    prefix,
    cursor,
    include: ["customMetadata"]
  })
  
  return c.json({
    files: listed.objects.map(obj => ({
      key: obj.key,
      size: obj.size,
      uploaded: obj.uploaded,
      metadata: obj.customMetadata
    })),
    cursor: listed.truncated ? listed.cursor : null
  })
})

Commands

# Create bucket
wrangler r2 bucket create my-bucket

# Upload file
wrangler r2 object put my-bucket/file.txt --file=./local.txt

# List objects
wrangler r2 object list my-bucket

# Delete
wrangler r2 object delete my-bucket/file.txt

Resources

Install

Download ZIP
Requires askill CLI v1.0+

AI Quality Score

95/100Analyzed 2/12/2026

Excellent technical reference for Cloudflare R2. Covers all major operations with clear TypeScript examples and CLI commands. Includes performance tips and common patterns.

90
95
90
95
95

Metadata

Licenseunknown
Version-
Updated2/5/2026
Publishermajiayu000

Tags

No tags yet.