| name | cloudflare-r2-bucket-management-and-access |
| description | Use this skill to configure Cloudflare R2 buckets, access control, lifecycle policies, public/private assets, and integration with Hono Workers using read/write/delete/list operations and signed-access patterns. |
Cloudflare R2 Bucket Management & Access Skill
Purpose
You are the assistant responsible for managing R2 storage in production including:
- Bucket provisioning & environment separation
- Public/private access strategy
- Upload/download/stream patterns
- Lifecycle rules & retention policies
- Signed URL access patterns
- Asset optimization + access performance
Use this skill when handling Cloudflare R2 + Hono Worker integration beyond basic uploads.
Do not use this skill for:
- D1 schema or migrations →
cloudflare-d1-migrations-and-production-seeding - Core Hono routing →
hono-app-scaffold - Auth logic →
hono-authentication
Environment-Aware Bucket Setup
This skill knows how to structure R2 per environment:
[[r2_buckets]]
binding = "BUCKET"
bucket_name = "my-bucket-dev"
preview_bucket_name = "my-bucket-dev"
[env.staging]
[[env.staging.r2_buckets]]
binding = "BUCKET"
bucket_name = "my-bucket-staging"
[env.production]
[[env.production.r2_buckets]]
binding = "BUCKET"
bucket_name = "my-bucket-prod"
Claude should:
- Default to separate buckets per environment
- Suggest mirroring structure between staging & prod
- Prevent mixing prod bucket during dev
R2 Access Permission Strategy
R2 can be accessed in multiple ways — this skill helps choose between:
| Mode | Use Case |
|---|---|
| Private (default) | User uploads, internal assets |
| Public Read | Images, static assets, public files |
| Signed-access URLs | Limited-time external file sharing |
| Proxy download route via Hono | Controlled rule-based access |
This skill selects approach based on context.
Best practice: Keep buckets private, expose objects through Worker/Routes or signed URLs.
Private Access (Recommended Default)
Use Cloudflare Worker as controlled access:
app.get("/file/:key", async (c) => {
const key = decodeURIComponent(c.req.param("key"));
const obj = await c.env.BUCKET.get(key);
if (!obj) return c.notFound();
return new Response(obj.body, {
headers: { "Content-Type": obj.httpMetadata?.contentType ?? "application/octet-stream" }
});
});
This enables auth-check:
if (!c.get("user")) return c.json({ auth: false }, 401);
Public Access Mode
Enable public read with bucket policy:
wrangler r2 bucket policy put BUCKET << 'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "r2:GetObject",
"Resource": "BUCKET/*"
}
]
}
EOF
Use only for assets intended to be publicly hosted (CDN optimized).
Upload Handling (secure)
const file = await c.req.formData().then(f => f.get("file") as File);
const key = `users/${c.get("user")?.id}/${Date.now()}-${file.name}`;
await c.env.BUCKET.put(key, await file.arrayBuffer(), {
httpMetadata: { contentType: file.type }
});
return c.json({ key, url: `/file/${encodeURIComponent(key)}` });
The skill ensures safe naming conventions:
- No raw user-supplied keys
- Timestamp or UUID prefix
- Supports multi-tenant structures
R2 Lifecycle Policies
Claude may apply retention or expiration policy suggestions:
| Policy | Benefit |
|---|---|
| Auto-delete after X days | Temporary file cleanup |
| Move to cold storage after X | Cost reduction |
| Prevent overwrite unless flagged | Audit safety |
Example JSON policy reference:
{
"Rules": [
{
"ID": "auto-expire-temp",
"Filter": { "Prefix": "temp/" },
"Expiration": { "Days": 30 }
}
]
}
Signed URL Access (download/share)
Cloudflare does not sign natively — we generate JWT-contained key + expiry:
import { SignJWT, jwtVerify } from "jose";
export async function createSignedURL(key: string, expSec: number, secret: string) {
const token = await new SignJWT({ key })
.setProtectedHeader({ alg: "HS256" })
.setExpirationTime(Math.floor(Date.now()/1000) + expSec)
.sign(new TextEncoder().encode(secret));
return `/download/${token}`;
}
app.get("/download/:token", async (c) => {
const secret = c.env.JWT_SECRET;
const { payload } = await jwtVerify(c.req.param("token"), new TextEncoder().encode(secret));
const obj = await c.env.BUCKET.get(payload.key);
if(!obj) return c.notFound();
return new Response(obj.body);
});
This simulates presigned URLs safely.
Directory-like Prefixing
Claude may suggest folder-style structure for organization:
/public/assets/*
/users/[id]/uploads/*
/tenants/[id]/invoices/*
/tmp/uploads/*
Naming rules enforced by this skill:
- Avoid exposing tenant IDs publicly unless hashed
- PascalCase or kebab-case predictable
- Versionable storage layout (
v1/avatars) for migrations
Listing + Search
const result = await c.env.BUCKET.list({ prefix: `users/${id}/` });
return c.json(result.objects.map(o => ({ key: o.key, size: o.size, date: o.uploaded })));
Skill advises pagination if results exceed limits.
CI/CD Hooks For R2 Deployments
This skill suggests combining with future CI/CD workflow:
wrangler deploy --env production
Then apply bucket policy or migration tasks post-deploy.
For automation:
- cache invalidation
- namespace provisioning
- retention checks
Example Prompts That Activate This Skill
- “Store files in R2 with security rules.”
- “Make public CDN bucket for static assets.”
- “Generate signed URLs for temporary downloads.”
- “Organize tenant bucket structure cleanly.”
- “Set expiration policy for old file uploads.”
Interactions With Other Skills
| Skill | How it connects |
|---|---|
hono-r2-integration |
This skill expands it with policy, access control, public/private modes |
cloudflare-worker-deployment |
Ensures R2 bindings are correctly mapped across envs |
cloudflare-d1-migrations-and-production-seeding |
Not DB schema — but both require env-controlled versioning |
hono-authentication |
User-based folder ownership, signed URL verification |