agentleFS
omspradippatilomspradippatil/Claude-Skills/.claude/skills/cloudflare-expert/SKILL.md

cloudflare-expert

Skill3 starsChanged 10 days agoMITView on GitHub

Activate this skill for Cloudflare Workers, Edge functions, D1 databases, R2 storage, or Wrangler deployments.

  • Reads credentials
  • Sends data out

We split this skill into the rules it gives an agent

6 rules. About 0 are common, 2 are distinctive, and 4 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈2

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 4

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    No full-text search (FTS) in D1 currently

    line 139
  2. Distinctive:

    Use the R2 public bucket URL or generate via a Worker that verifies auth before returning an object — R2 does not have native pre-signed …

    line 173
  3. Very specific:

    Never: put secrets in wrangler.toml — use wrangler secret put instead

    line 305
  4. Very specific:

    Use crypto.subtle (available in Workers) for HMAC verification of webhooks:

    line 308
  5. Very specific:

    Use ctx.waitUntil() for non-blocking background work (logging, cache writes)

    line 324
  6. Very specific:

    Use Promise.all() for parallel fetch/D1 calls that don't depend on each other

    line 328

≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Cloudflare Expert Skill

Cloudflare Workers

Handler Patterns

// Module syntax (recommended)
export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const url = new URL(request.url);

    // Route by pathname
    if (url.pathname.startsWith('/api/')) {
      return handleApi(request, env, ctx);
    }

    return new Response('Not Found', { status: 404 });
  },

  // Scheduled trigger
  async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext) {
    ctx.waitUntil(runCleanup(env));
  },
};

// TypeScript bindings interface
interface Env {
  DB: D1Database;
  BUCKET: R2Bucket;
  KV: KVNamespace;
  API_SECRET: string; // secret set via wrangler secret
}

Request/Response Manipulation

// Clone and modify request headers
const modifiedRequest = new Request(request, {
  headers: { ...Object.fromEntries(request.headers), 'X-Custom': 'value' },
});

// Stream large responses
async function handleStream(request: Request): Promise<Response> {
  const { readable, writable } = new TransformStream();
  // pipe upstream to writable
  return new Response(readable, {
    headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache' },
  });
}

// CORS response helper
function corsResponse(body: string, status = 200): Response {
  return new Response(body, {
    status,
    headers: {
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type, Authorization',
      'Content-Type': 'application/json',
    },
  });
}

Environment Bindings

Defined in wrangler.toml:

name = "my-worker"
main = "src/index.ts"
compatibility_date = "2024-01-01"

[[d1_databases]]
binding = "DB"
database_name = "my-db"
database_id = "xxxx-xxxx"

[[r2_buckets]]
binding = "BUCKET"
bucket_name = "my-bucket"

[[kv_namespaces]]
binding = "KV"
id = "xxxx"

D1 — Edge SQL Database

Query Patterns

// Prepared statement (prevents SQL injection)
const stmt = env.DB.prepare('SELECT * FROM users WHERE id = ?').bind(userId);
const user = await stmt.first<User>();

// Batch queries (single round trip)
const results = await env.DB.batch([
  env.DB.prepare('INSERT INTO posts (title, author_id) VALUES (?, ?)').bind(title, authorId),
  env.DB.prepare('UPDATE users SET post_count = post_count + 1 WHERE id = ?').bind(authorId),
]);

// All results
const { results: posts } = await env.DB.prepare('SELECT * FROM posts ORDER BY created_at DESC LIMIT ?').bind(50).all<Post>();

Migrations

# Create migration
wrangler d1 migrations create my-db add-users-table

# Apply locally
wrangler d1 migrations apply my-db --local

# Apply to production
wrangler d1 migrations apply my-db --remote
-- migrations/0001_add_users_table.sql
CREATE TABLE IF NOT EXISTS users (
  id TEXT PRIMARY KEY,
  email TEXT NOT NULL UNIQUE,
  created_at INTEGER NOT NULL DEFAULT (unixepoch())
);
CREATE INDEX idx_users_email ON users(email);

SQLite Compatibility Notes

  • D1 uses SQLite under the hood — use SQLite types: TEXT, INTEGER, REAL, BLOB
  • No RETURNING clause support in older compatibility dates — query after insert
  • Timestamps: store as INTEGER (Unix epoch) or TEXT (ISO 8601)
  • No full-text search (FTS) in D1 currently

R2 — Object Storage

Operations

// Upload
await env.BUCKET.put('uploads/image.jpg', request.body, {
  httpMetadata: { contentType: 'image/jpeg' },
  customMetadata: { userId: '123' },
});

// Download
const object = await env.BUCKET.get('uploads/image.jpg');
if (!object) return new Response('Not Found', { status: 404 });

return new Response(object.body, {
  headers: {
    'Content-Type': object.httpMetadata?.contentType ?? 'application/octet-stream',
    'Cache-Control': 'public, max-age=31536000, immutable',
    'ETag': object.etag,
  },
});

// Delete
await env.BUCKET.delete('uploads/image.jpg');

// List
const listed = await env.BUCKET.list({ prefix: 'uploads/', limit: 100 });

Pre-signed URLs

Use the R2 public bucket URL or generate via a Worker that verifies auth before returning an object — R2 does not have native pre-signed URLs like S3. Use time-limited signed tokens in your Worker instead.

CORS Configuration

Configure in R2 Dashboard or via API:

[
  {
    "AllowedOrigins": ["https://your-app.com"],
    "AllowedMethods": ["GET", "PUT"],
    "AllowedHeaders": ["*"],
    "MaxAgeSeconds": 3000
  }
]

KV — Key-Value Store

Patterns

// Write with TTL (seconds)
await env.KV.put('session:abc123', JSON.stringify(sessionData), {
  expirationTtl: 3600, // 1 hour
});

// Read with type helper
const raw = await env.KV.get('session:abc123');
const session = raw ? (JSON.parse(raw) as Session) : null;

// Read with metadata
const { value, metadata } = await env.KV.getWithMetadata<Meta>('key');

// List keys with prefix
const { keys } = await env.KV.list({ prefix: 'session:' });

Eventual Consistency

  • KV is eventually consistent — writes may take up to 60s to propagate globally
  • Not suitable for counters, leaderboards, or highly consistent transactional data — use D1 for that
  • Reads are always from the closest edge — great for config, feature flags, cached HTML

Wrangler CLI

Local Development

# Start local dev server (uses local miniflare)
wrangler dev

# Dev against production bindings (caution!)
wrangler dev --remote

# Tail production logs
wrangler tail

Secrets

# Set a secret (prompts for value, never stored in wrangler.toml)
wrangler secret put STRIPE_SECRET_KEY

# List secrets
wrangler secret list

# Delete a secret
wrangler secret delete STRIPE_SECRET_KEY

Environments

# wrangler.toml
[env.staging]
name = "my-worker-staging"
vars = { ENVIRONMENT = "staging" }

[env.production]
name = "my-worker-prod"
vars = { ENVIRONMENT = "production" }
wrangler deploy --env production
wrangler secret put API_KEY --env production

Edge Caching

Cache-Control Headers

// Cache static assets for 1 year with immutable
return new Response(asset, {
  headers: { 'Cache-Control': 'public, max-age=31536000, immutable' },
});

// Cache API responses for 60s, allow stale for 10s
return new Response(data, {
  headers: { 'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=10' },
});

Cache API (Programmatic)

const cache = caches.default;

async function fetch(request: Request, env: Env, ctx: ExecutionContext) {
  // Check cache first
  const cached = await cache.match(request);
  if (cached) return cached;

  // Fetch and store
  const response = await fetch(request.url);
  ctx.waitUntil(cache.put(request, response.clone()));
  return response;
}

Cache Purging

# Purge by tag (requires Enterprise) or by URL via API
curl -X POST "https://api.cloudflare.com/client/v4/zones/{zone_id}/purge_cache" \
  -H "Authorization: Bearer $CF_API_TOKEN" \
  -d '{"files": ["https://example.com/static/main.js"]}'

Security Best Practices

  • Never put secrets in wrangler.toml — use wrangler secret put instead
  • Validate Authorization headers or use Cloudflare Access for admin endpoints
  • Set Access-Control-Allow-Origin explicitly — avoid wildcard * for credentialed requests
  • Use crypto.subtle (available in Workers) for HMAC verification of webhooks:
async function verifySignature(body: string, signature: string, secret: string): Promise<boolean> {
  const key = await crypto.subtle.importKey(
    'raw', new TextEncoder().encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['verify']
  );
  const sig = hexToUint8Array(signature);
  return crypto.subtle.verify('HMAC', key, sig, new TextEncoder().encode(body));
}

Performance Best Practices

  • Minimize CPU time: Workers have a 50ms CPU limit on free tier (50ms–30s on paid)
  • Use ctx.waitUntil() for non-blocking background work (logging, cache writes)
  • Stream large responses using TransformStream rather than buffering
  • Co-locate D1 databases and Workers in the same region to minimize latency
  • Cache expensive D1 queries in KV with appropriate TTLs
  • Use Promise.all() for parallel fetch/D1 calls that don't depend on each other

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

Reviews can't be read right now.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.