Agent skill

Cloudflare Workers

by einverne in einverne/dotfiles

Comprehensive guide for building serverless applications with Cloudflare Workers.

MITAuto-check passedBackend & APIs

Install Cloudflare Workers

skills CLI
$ npx skills add einverne/dotfiles --skill cloudflare-workers -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install einverne/dotfiles cloudflare-workers --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/einverne/dotfiles.git skills-src && mkdir -p .claude/skills && cp -r skills-src/claude/skills/cloudflare-workers .claude/skills/cloudflare-workers && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
cloudflare-workers
GitHub stars
121
Token cost
~8.4k tokens
SKILL.md length
631 words
Files
1
Skills in repo
39
Repo updated
First seen
Licence
MIT

At a glance

Comprehensive guide for building serverless applications with Cloudflare Workers.

  • Works in 4 steps: Request arrives at nearest Cloudflare… → Worker executes in V8 isolate → Response returned to client → …
  • Developing Workers
  • SKILL.md covers Reference, When to Use This Skill, Core Concepts and Wrangler CLI, plus 2 more sections
  • Calls wrangler, npm and npx; needs API_KEY and CLOUDFLARE_API_TOKEN

What it does

Cloudflare Workers is an agent skill from einverne/dotfiles. Comprehensive guide for building serverless applications with Cloudflare Workers. Use when developing Workers, configuring bindings, implementing runtime APIs, testing Workers, using Wrangler CLI, deploying to production, or building edge functions with JavaScript/TypeScript/Python/Rust.

Its SKILL.md is about 8.4k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.

It sits in Backend & APIs, covering Serverless. It works with Cloudflare Workers, JavaScript, Python and Rust. The repository describes itself as: my personal dotfiles managed by dotbot, zinit. The licence is MIT.

When your agent uses it

  • Developing Workers
  • Configuring bindings
  • Implementing runtime APIs
  • Testing Workers

Example prompts

  • “/cloudflare-workers”

Requirements

  • Node.js
  • A credential in API_KEY

Workflow steps

4 steps, taken from the first numbered list in SKILL.md.

  1. Request arrives at nearest Cloudflare data center
  2. Worker executes in V8 isolate
  3. Response returned to client
  4. Isolate may be reused for subsequent requests

What it can do on your machine

Read from SKILL.md and the folder at commit c6c0686. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Shell commands in SKILL.md call:

    • wrangler
    • npm
    • npx
    • curl

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    Links to these hosts (documentation or services it may open):

    • developers.cloudflare.com
    • discord.cloudflare.com
    • github.com
    • cloudflarestatus.com

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names these keys or tokens, usually read from environment variables:

    • API_KEY
    • CLOUDFLARE_API_TOKEN
    • JWT_SECRET
    • WEBHOOK_SECRET

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Cloudflare Workers loads about 8.4k tokens when it runs. Until then it costs about 77 tokens; SKILL.md has 631 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~77
When it runs · the whole SKILL.md, loaded when a task matches
~8.4k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from einverne/dotfiles at commit c6c0686, republished under its MIT licence (© einverne). 631 words, ~8,420 tokens.

Download SKILL.mdSave it as .claude/skills/cloudflare-workers/SKILL.md (or your agent's skills folder).
name
cloudflare-workers
description
Comprehensive guide for building serverless applications with Cloudflare Workers. Use when developing Workers, configuring bindings, implementing runtime APIs, testing Workers, using Wrangler CLI, deploying to production, or building edge functions with JavaScript/TypeScript/Python/Rust.
license
MIT
version
1.0.0

Cloudflare Workers Skill

Cloudflare Workers is a serverless execution environment that runs JavaScript, TypeScript, Python, and Rust code on Cloudflare's global edge network across 300+ cities worldwide.

Reference

When to Use This Skill

Use this skill when:

  • Creating new Cloudflare Workers applications
  • Configuring Worker bindings (D1, KV, R2, Durable Objects, etc.)
  • Implementing Worker runtime APIs (fetch, cache, HTMLRewriter, WebSockets)
  • Writing and testing Workers locally with Wrangler
  • Deploying Workers to production
  • Implementing cron triggers and scheduled jobs
  • Building API endpoints and middleware
  • Working with Workers static assets
  • Setting up CI/CD for Workers
  • Migrating from service workers to ES modules
  • Debugging and monitoring Workers
  • Optimizing Worker performance

Core Concepts

Execution Model

V8 Isolates: Workers run in lightweight V8 isolates (not containers):

  • Millisecond cold starts (faster than containers)
  • Zero infrastructure management
  • Automatic global scaling
  • Pay-per-request pricing

Request Lifecycle:

  1. Request arrives at nearest Cloudflare data center
  2. Worker executes in V8 isolate
  3. Response returned to client
  4. Isolate may be reused for subsequent requests

Key Characteristics:

  • Maximum CPU time: 50ms (Free), 30 seconds (Paid)
  • Maximum memory: 128MB
  • Executes at the edge (closest to user)
  • No state between requests (use Durable Objects for state)
Worker Formats

ES Modules (Recommended):

typescript
// Modern syntax with export default
export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    return new Response('Hello World!');
  }
};

Service Worker (Legacy):

javascript
// Legacy format
addEventListener('fetch', (event) => {
  event.respondWith(new Response('Hello World!'));
});
Handler Types

Workers support multiple event types:

Fetch Handler (HTTP requests):

typescript
export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    return new Response('Hello');
  }
};

Scheduled Handler (Cron jobs):

typescript
export default {
  async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext): Promise<void> {
    // Runs on schedule
    await fetch('https://api.example.com/cleanup');
  }
};

Queue Handler (Message processing):

typescript
export default {
  async queue(batch: MessageBatch, env: Env, ctx: ExecutionContext): Promise<void> {
    for (const message of batch.messages) {
      await processMessage(message.body);
    }
  }
};

Email Handler (Email routing):

typescript
export default {
  async email(message: ForwardableEmailMessage, env: Env, ctx: ExecutionContext): Promise<void> {
    await message.forward('destination@example.com');
  }
};

Tail Handler (Log aggregation):

typescript
export default {
  async tail(events: TraceItem[], env: Env, ctx: ExecutionContext): Promise<void> {
    // Process logs from other Workers
  }
};

Wrangler CLI

Installation & Setup
bash
# Install globally
npm install -g wrangler

# Or use npx
npx wrangler <command>

# Login to Cloudflare
wrangler login

# Check version
wrangler --version
Project Management
bash
# Create new Worker
wrangler init my-worker
# Options: TypeScript, Git, deploy

# Create with template
wrangler init my-worker --template <template-url>

# Development server
wrangler dev                    # Local mode
wrangler dev --remote          # Remote mode (actual edge)
wrangler dev --port 8080       # Custom port

# Deploy
wrangler deploy                # Production
wrangler deploy --dry-run      # Preview changes
wrangler deploy --env staging  # Specific environment

# Logs
wrangler tail                  # Real-time logs
wrangler tail --format pretty  # Formatted output
wrangler tail --status error   # Filter by status

# Versions & Rollback
wrangler deployments list
wrangler rollback [version-id]
wrangler versions list
Configuration (wrangler.toml)
toml
name = "my-worker"
main = "src/index.ts"
compatibility_date = "2024-01-01"
compatibility_flags = ["nodejs_compat"]

# Routes
routes = [
  { pattern = "example.com/*", zone_name = "example.com" }
]

# Or use custom domains
# [[routes]]
# pattern = "api.example.com/*"
# custom_domain = true

# Environment variables
[vars]
ENVIRONMENT = "production"
API_VERSION = "v1"

# Cron triggers
[triggers]
crons = ["0 0 * * *"]  # Daily at midnight

# Build configuration
[build]
command = "npm run build"
watch_dirs = ["src"]

# Environments
[env.staging]
name = "my-worker-staging"
routes = [
  { pattern = "staging.example.com/*", zone_name = "example.com" }
]

[env.staging.vars]
ENVIRONMENT = "staging"
Secrets Management
bash
# Add secret
wrangler secret put API_KEY
# Enter value when prompted

# List secrets
wrangler secret list

# Delete secret
wrangler secret delete API_KEY

# Bulk upload
echo "VALUE" | wrangler secret put API_KEY

Runtime APIs

Fetch API
typescript
export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    // Parse URL
    const url = new URL(request.url);

    // Request properties
    const method = request.method;
    const headers = request.headers;
    const body = await request.text();

    // Subrequest
    const response = await fetch('https://api.example.com/data', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ key: 'value' })
    });

    const data = await response.json();

    // Response
    return new Response(JSON.stringify(data), {
      status: 200,
      headers: {
        'Content-Type': 'application/json',
        'Cache-Control': 'public, max-age=3600'
      }
    });
  }
};
Headers API
typescript
// Read headers
const userAgent = request.headers.get('User-Agent');
const allHeaders = Object.fromEntries(request.headers);

// Set headers
const headers = new Headers();
headers.set('Content-Type', 'application/json');
headers.append('X-Custom-Header', 'value');

// Cloudflare-specific headers
const country = request.cf?.country;
const colo = request.cf?.colo;
const clientIP = request.headers.get('CF-Connecting-IP');

// Response with headers
return new Response(body, { headers });
Cache API
typescript
export default {
  async fetch(request: Request): Promise<Response> {
    const cache = caches.default;

    // Check cache
    let response = await cache.match(request);

    if (!response) {
      // Cache miss - fetch from origin
      response = await fetch(request);

      // Clone and cache response
      const cacheResponse = response.clone();
      ctx.waitUntil(cache.put(request, cacheResponse));
    }

    return response;
  }
};

Cache with custom key:

typescript
const cacheKey = new Request(url.toString(), request);
const response = await cache.match(cacheKey);

if (!response) {
  response = await fetch(request);
  ctx.waitUntil(cache.put(cacheKey, response.clone()));
}

Cache with TTL:

typescript
const response = await fetch('https://api.example.com/data', {
  cf: {
    cacheTtl: 3600,
    cacheEverything: true,
    cacheKey: 'custom-key'
  }
});
HTMLRewriter
typescript
export default {
  async fetch(request: Request): Promise<Response> {
    const response = await fetch(request);

    return new HTMLRewriter()
      .on('title', {
        element(element) {
          element.setInnerContent('New Title');
        }
      })
      .on('a[href]', {
        element(element) {
          const href = element.getAttribute('href');
          element.setAttribute('href', href.replace('http://', 'https://'));
        }
      })
      .on('script', {
        element(element) {
          element.remove();
        }
      })
      .transform(response);
  }
};

Text manipulation:

typescript
new HTMLRewriter()
  .on('p', {
    text(text) {
      if (text.text.includes('replace-me')) {
        text.replace('new-text');
      }
    }
  })
  .transform(response);
WebSockets
typescript
export default {
  async fetch(request: Request): Promise<Response> {
    const upgradeHeader = request.headers.get('Upgrade');

    if (upgradeHeader !== 'websocket') {
      return new Response('Expected WebSocket', { status: 426 });
    }

    const pair = new WebSocketPair();
    const [client, server] = Object.values(pair);

    // Accept WebSocket connection
    server.accept();

    // Handle messages
    server.addEventListener('message', (event) => {
      server.send(`Echo: ${event.data}`);
    });

    server.addEventListener('close', () => {
      console.log('WebSocket closed');
    });

    return new Response(null, {
      status: 101,
      webSocket: client
    });
  }
};
Streams API
typescript
// Readable stream
const { readable, writable } = new TransformStream();

const writer = writable.getWriter();
writer.write(new TextEncoder().encode('chunk 1'));
writer.write(new TextEncoder().encode('chunk 2'));
writer.close();

return new Response(readable, {
  headers: { 'Content-Type': 'text/plain' }
});

Stream transformation:

typescript
const response = await fetch('https://example.com/large-file');

const { readable, writable } = new TransformStream({
  transform(chunk, controller) {
    // Process chunk
    controller.enqueue(chunk);
  }
});

response.body.pipeTo(writable);

return new Response(readable);
Context API
typescript
export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    // waitUntil: Run tasks after response sent
    ctx.waitUntil(
      fetch('https://analytics.example.com/log', {
        method: 'POST',
        body: JSON.stringify({ url: request.url })
      })
    );

    // passThroughOnException: Continue to origin on error
    ctx.passThroughOnException();

    return new Response('OK');
  }
};
Web Crypto API
typescript
// Generate hash
const data = new TextEncoder().encode('message');
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');

// HMAC signature
const key = await crypto.subtle.importKey(
  'raw',
  new TextEncoder().encode('secret'),
  { name: 'HMAC', hash: 'SHA-256' },
  false,
  ['sign', 'verify']
);

const signature = await crypto.subtle.sign('HMAC', key, data);

// Verify
const valid = await crypto.subtle.verify('HMAC', key, signature, data);

// Random values
const randomBytes = crypto.getRandomValues(new Uint8Array(32));
const uuid = crypto.randomUUID();

// Timing-safe comparison
const equal = crypto.timingSafeEqual(buffer1, buffer2);

Bindings

Environment Variables
typescript
// wrangler.toml
[vars]
API_URL = "https://api.example.com"

// Usage
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const apiUrl = env.API_URL;
    return new Response(apiUrl);
  }
};
KV Namespace
toml
# wrangler.toml
[[kv_namespaces]]
binding = "MY_KV"
id = "your-namespace-id"
typescript
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    // Put
    await env.MY_KV.put('key', 'value', {
      expirationTtl: 3600,
      metadata: { userId: '123' }
    });

    // Get
    const value = await env.MY_KV.get('key');
    const json = await env.MY_KV.get('key', 'json');
    const buffer = await env.MY_KV.get('key', 'arrayBuffer');
    const stream = await env.MY_KV.get('key', 'stream');

    // Get with metadata
    const { value, metadata } = await env.MY_KV.getWithMetadata('key');

    // Delete
    await env.MY_KV.delete('key');

    // List
    const list = await env.MY_KV.list({ prefix: 'user:' });

    return new Response(value);
  }
};
R2 Bucket
toml
# wrangler.toml
[[r2_buckets]]
binding = "MY_BUCKET"
bucket_name = "my-bucket"
typescript
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    // Put object
    await env.MY_BUCKET.put('file.txt', 'content', {
      httpMetadata: {
        contentType: 'text/plain',
        contentLanguage: 'en-US',
        cacheControl: 'public, max-age=3600'
      },
      customMetadata: {
        uploadedBy: 'user123'
      }
    });

    // Get object
    const object = await env.MY_BUCKET.get('file.txt');

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

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

    // Delete object
    await env.MY_BUCKET.delete('file.txt');

    // List objects
    const listed = await env.MY_BUCKET.list({ prefix: 'uploads/' });

    // Multipart upload
    const multipart = await env.MY_BUCKET.createMultipartUpload('large-file.bin');
    const part1 = await multipart.uploadPart(1, chunk1);
    const part2 = await multipart.uploadPart(2, chunk2);
    await multipart.complete([part1, part2]);
  }
};
D1 Database
toml
# wrangler.toml
[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "your-database-id"
typescript
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    // Query
    const result = await env.DB.prepare(
      'SELECT * FROM users WHERE id = ?'
    ).bind(userId).first();

    // Insert
    const info = await env.DB.prepare(
      'INSERT INTO users (name, email) VALUES (?, ?)'
    ).bind('Alice', 'alice@example.com').run();

    // Batch (atomic)
    const results = await env.DB.batch([
      env.DB.prepare('UPDATE accounts SET balance = balance - 100 WHERE id = ?').bind(1),
      env.DB.prepare('UPDATE accounts SET balance = balance + 100 WHERE id = ?').bind(2)
    ]);

    // All results
    const { results: rows } = await env.DB.prepare(
      'SELECT * FROM users'
    ).all();

    return new Response(JSON.stringify(result));
  }
};
Durable Objects
toml
# wrangler.toml
[[durable_objects.bindings]]
name = "COUNTER"
class_name = "Counter"
script_name = "my-worker"
typescript
// Define Durable Object
export class Counter {
  state: DurableObjectState;

  constructor(state: DurableObjectState, env: Env) {
    this.state = state;
  }

  async fetch(request: Request): Promise<Response> {
    let count = (await this.state.storage.get<number>('count')) || 0;
    count++;
    await this.state.storage.put('count', count);

    return new Response(JSON.stringify({ count }));
  }

  // Alarm handler
  async alarm(): Promise<void> {
    // Runs at scheduled time
    await this.state.storage.deleteAll();
  }
}

// Use in Worker
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const id = env.COUNTER.idFromName('global');
    const counter = env.COUNTER.get(id);
    return counter.fetch(request);
  }
};
Queues
toml
# wrangler.toml
[[queues.producers]]
binding = "MY_QUEUE"
queue = "my-queue"

[[queues.consumers]]
queue = "my-queue"
max_batch_size = 10
max_batch_timeout = 30
typescript
// Producer
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    await env.MY_QUEUE.send({
      type: 'email',
      to: 'user@example.com'
    });

    // Batch send
    await env.MY_QUEUE.sendBatch([
      { body: { message: 1 } },
      { body: { message: 2 } }
    ]);

    return new Response('Queued');
  }
};

// Consumer
export default {
  async queue(batch: MessageBatch, env: Env, ctx: ExecutionContext): Promise<void> {
    for (const message of batch.messages) {
      try {
        await processMessage(message.body);
        message.ack();
      } catch (error) {
        message.retry();
      }
    }
  }
};
Workers AI
toml
# wrangler.toml
[ai]
binding = "AI"
typescript
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    // Text generation
    const response = await env.AI.run('@cf/meta/llama-3-8b-instruct', {
      messages: [
        { role: 'user', content: 'What is edge computing?' }
      ]
    });

    // Image generation
    const imageResponse = await env.AI.run('@cf/stabilityai/stable-diffusion-xl-base-1.0', {
      prompt: 'A sunset over mountains'
    });

    // Embeddings
    const embeddings = await env.AI.run('@cf/baai/bge-base-en-v1.5', {
      text: ['Hello world', 'Cloudflare Workers']
    });

    // Speech recognition
    const audio = await request.arrayBuffer();
    const transcription = await env.AI.run('@cf/openai/whisper', {
      audio: [...new Uint8Array(audio)]
    });

    return new Response(JSON.stringify(response));
  }
};
Vectorize (Vector Database)
toml
# wrangler.toml
[[vectorize]]
binding = "VECTORIZE_INDEX"
index_name = "my-index"
typescript
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    // Insert vectors
    await env.VECTORIZE_INDEX.insert([
      {
        id: '1',
        values: [0.1, 0.2, 0.3],
        metadata: { text: 'Hello world' }
      }
    ]);

    // Query
    const results = await env.VECTORIZE_INDEX.query(
      [0.1, 0.2, 0.3],
      { topK: 5 }
    );

    return new Response(JSON.stringify(results));
  }
};

Development Patterns

Routing
typescript
export default {
  async fetch(request: Request): Promise<Response> {
    const url = new URL(request.url);

    // Route by path
    switch (url.pathname) {
      case '/':
        return new Response('Home');
      case '/about':
        return new Response('About');
      default:
        return new Response('Not Found', { status: 404 });
    }
  }
};

Using Hono framework:

typescript
import { Hono } from 'hono';

const app = new Hono();

app.get('/', (c) => c.text('Home'));
app.get('/api/users/:id', async (c) => {
  const id = c.req.param('id');
  const user = await getUser(id);
  return c.json(user);
});

export default app;
Middleware Pattern
typescript
async function auth(request: Request, env: Env): Promise<Response | null> {
  const token = request.headers.get('Authorization');

  if (!token) {
    return new Response('Unauthorized', { status: 401 });
  }

  return null; // Continue
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const authResponse = await auth(request, env);
    if (authResponse) return authResponse;

    // Continue with request
    return new Response('Protected content');
  }
};
Error Handling
typescript
export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    try {
      const response = await processRequest(request, env);
      return response;
    } catch (error) {
      console.error('Error:', error);

      // Log to external service
      ctx.waitUntil(
        fetch('https://logging.example.com/error', {
          method: 'POST',
          body: JSON.stringify({
            error: error.message,
            stack: error.stack,
            url: request.url
          })
        })
      );

      return new Response('Internal Server Error', { status: 500 });
    }
  }
};
CORS
typescript
function corsHeaders(origin: string) {
  return {
    'Access-Control-Allow-Origin': origin,
    'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
    'Access-Control-Allow-Headers': 'Content-Type, Authorization',
    'Access-Control-Max-Age': '86400'
  };
}

export default {
  async fetch(request: Request): Promise<Response> {
    const origin = request.headers.get('Origin') || '*';

    // Handle preflight
    if (request.method === 'OPTIONS') {
      return new Response(null, {
        headers: corsHeaders(origin)
      });
    }

    // Handle request
    const response = await handleRequest(request);

    // Add CORS headers
    const headers = new Headers(response.headers);
    Object.entries(corsHeaders(origin)).forEach(([key, value]) => {
      headers.set(key, value);
    });

    return new Response(response.body, {
      status: response.status,
      headers
    });
  }
};
Rate Limiting
typescript
async function rateLimit(ip: string, env: Env): Promise<boolean> {
  const key = `ratelimit:${ip}`;
  const limit = 100;
  const window = 60;

  const current = await env.MY_KV.get(key);
  const count = current ? parseInt(current) : 0;

  if (count >= limit) {
    return false;
  }

  await env.MY_KV.put(key, (count + 1).toString(), {
    expirationTtl: window
  });

  return true;
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const ip = request.headers.get('CF-Connecting-IP') || 'unknown';

    if (!await rateLimit(ip, env)) {
      return new Response('Rate limit exceeded', { status: 429 });
    }

    return new Response('OK');
  }
};

Testing

Local Testing with Wrangler
bash
# Start dev server
wrangler dev

# Test with curl
curl http://localhost:8787

# Remote development (uses actual edge)
wrangler dev --remote
Unit Testing with Vitest
bash
npm install -D vitest @cloudflare/vitest-pool-workers

vitest.config.ts:

typescript
import { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config';

export default defineWorkersConfig({
  test: {
    poolOptions: {
      workers: {
        wrangler: { configPath: './wrangler.toml' },
      },
    },
  },
});

Test file:

typescript
import { env, createExecutionContext, waitOnExecutionContext } from 'cloudflare:test';
import { describe, it, expect } from 'vitest';
import worker from '../src/index';

describe('Worker', () => {
  it('responds with Hello World', async () => {
    const request = new Request('http://example.com');
    const ctx = createExecutionContext();
    const response = await worker.fetch(request, env, ctx);
    await waitOnExecutionContext(ctx);

    expect(await response.text()).toBe('Hello World!');
  });
});
Integration Testing
typescript
import { unstable_dev } from 'wrangler';

describe('Worker integration tests', () => {
  let worker;

  beforeAll(async () => {
    worker = await unstable_dev('src/index.ts', {
      experimental: { disableExperimentalWarning: true }
    });
  });

  afterAll(async () => {
    await worker.stop();
  });

  it('should return 200', async () => {
    const resp = await worker.fetch();
    expect(resp.status).toBe(200);
  });
});

Deployment

Basic Deployment
bash
# Deploy to production
wrangler deploy

# Deploy to specific environment
wrangler deploy --env staging

# Preview deployment
wrangler deploy --dry-run
Environments
toml
# wrangler.toml
name = "my-worker"

[env.staging]
name = "my-worker-staging"
routes = [
  { pattern = "staging.example.com/*", zone_name = "example.com" }
]

[env.production]
name = "my-worker-production"
routes = [
  { pattern = "api.example.com/*", zone_name = "example.com" }
]
bash
wrangler deploy --env staging
wrangler deploy --env production
CI/CD with GitHub Actions
yaml
# .github/workflows/deploy.yml
name: Deploy Worker

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'

      - run: npm install

      - run: npm test

      - uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
Gradual Deployments
bash
# Deploy to 10% of traffic
wrangler versions deploy --percentage 10

# Promote to 100%
wrangler versions deploy --percentage 100

# Rollback
wrangler rollback

Static Assets

Configuration
toml
# wrangler.toml
name = "my-worker"
main = "src/index.ts"

[assets]
directory = "./public"
binding = "ASSETS"
Serving Static Assets
typescript
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    // Serve static asset
    const asset = await env.ASSETS.fetch(request);

    if (asset.status === 200) {
      return asset;
    }

    // Fallback to dynamic response
    return new Response('Not Found', { status: 404 });
  }
};
Single Page Application (SPA)
toml
[assets]
directory = "./dist"
binding = "ASSETS"
html_handling = "force-https"
not_found_handling = "single-page-application"

Observability

Logging
typescript
export default {
  async fetch(request: Request): Promise<Response> {
    console.log('Request URL:', request.url);
    console.error('Error occurred');
    console.warn('Warning message');

    // Structured logging
    console.log(JSON.stringify({
      level: 'info',
      message: 'Request processed',
      url: request.url,
      timestamp: new Date().toISOString()
    }));

    return new Response('OK');
  }
};
Real-time Logs
bash
# Tail logs
wrangler tail

# Filter by status
wrangler tail --status error

# Filter by method
wrangler tail --method POST

# Filter by sampling
wrangler tail --sampling-rate 0.5
Analytics
typescript
// Use Analytics Engine binding
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    env.ANALYTICS.writeDataPoint({
      blobs: ['example'],
      doubles: [123.45],
      indexes: ['index_value']
    });

    return new Response('Logged');
  }
};
Error Tracking (Sentry)
typescript
import * as Sentry from '@sentry/cloudflare';

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    Sentry.init({
      dsn: env.SENTRY_DSN,
      environment: env.ENVIRONMENT
    });

    try {
      return await handleRequest(request);
    } catch (error) {
      Sentry.captureException(error);
      throw error;
    }
  }
};

Performance Optimization

Show full SKILL.md (282 more words)Show less
Best Practices
  1. Minimize Bundle Size: Keep Workers under 1MB
  2. Use Bindings: Direct bindings are faster than fetch
  3. Cache Aggressively: Use Cache API and KV
  4. Stream Large Responses: Use streams instead of buffering
  5. Batch Operations: Combine D1 queries with batch()
  6. waitUntil for Background Tasks: Don't block response
  7. Avoid Synchronous I/O: All operations should be async
Code Splitting
typescript
// Lazy load large dependencies
export default {
  async fetch(request: Request): Promise<Response> {
    const url = new URL(request.url);

    if (url.pathname === '/heavy') {
      const { processHeavy } = await import('./heavy');
      return processHeavy(request);
    }

    return new Response('OK');
  }
};
Caching Strategy
typescript
const CACHE_TTL = 3600;

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const cache = caches.default;
    const cacheKey = new Request(request.url);

    // 1. Check edge cache
    let response = await cache.match(cacheKey);
    if (response) return response;

    // 2. Check KV cache
    const kvCached = await env.MY_KV.get(request.url);
    if (kvCached) {
      response = new Response(kvCached);
      ctx.waitUntil(cache.put(cacheKey, response.clone()));
      return response;
    }

    // 3. Fetch from origin
    response = await fetch(request);

    // 4. Store in both caches
    ctx.waitUntil(Promise.all([
      cache.put(cacheKey, response.clone()),
      env.MY_KV.put(request.url, await response.clone().text(), {
        expirationTtl: CACHE_TTL
      })
    ]));

    return response;
  }
};

Common Patterns

API Gateway
typescript
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { logger } from 'hono/logger';

const app = new Hono();

app.use('*', cors());
app.use('*', logger());

// Routes
app.get('/api/users', async (c) => {
  const users = await c.env.DB.prepare('SELECT * FROM users').all();
  return c.json(users.results);
});

app.get('/api/users/:id', async (c) => {
  const id = c.req.param('id');
  const user = await c.env.DB.prepare(
    'SELECT * FROM users WHERE id = ?'
  ).bind(id).first();

  if (!user) {
    return c.json({ error: 'Not found' }, 404);
  }

  return c.json(user);
});

app.post('/api/users', async (c) => {
  const body = await c.req.json();

  const result = await c.env.DB.prepare(
    'INSERT INTO users (name, email) VALUES (?, ?)'
  ).bind(body.name, body.email).run();

  return c.json({ id: result.meta.last_row_id }, 201);
});

export default app;
Authentication
typescript
import { sign, verify } from 'hono/jwt';

async function authenticate(request: Request, env: Env): Promise<any> {
  const authHeader = request.headers.get('Authorization');

  if (!authHeader?.startsWith('Bearer ')) {
    throw new Error('Missing token');
  }

  const token = authHeader.substring(7);
  const payload = await verify(token, env.JWT_SECRET);

  return payload;
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    try {
      const user = await authenticate(request, env);
      return new Response(`Hello ${user.name}`);
    } catch (error) {
      return new Response('Unauthorized', { status: 401 });
    }
  }
};
Image Proxy
typescript
export default {
  async fetch(request: Request): Promise<Response> {
    const url = new URL(request.url);
    const imageUrl = url.searchParams.get('url');

    if (!imageUrl) {
      return new Response('Missing url parameter', { status: 400 });
    }

    const response = await fetch(imageUrl);

    return new Response(response.body, {
      headers: {
        'Content-Type': response.headers.get('Content-Type') || 'image/jpeg',
        'Cache-Control': 'public, max-age=86400',
        'CDN-Cache-Control': 'public, max-age=31536000'
      }
    });
  }
};
Webhook Handler
typescript
export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    if (request.method !== 'POST') {
      return new Response('Method not allowed', { status: 405 });
    }

    // Verify signature
    const signature = request.headers.get('X-Hub-Signature-256');
    const body = await request.text();

    const valid = await verifySignature(body, signature, env.WEBHOOK_SECRET);

    if (!valid) {
      return new Response('Invalid signature', { status: 401 });
    }

    // Queue for processing
    ctx.waitUntil(
      env.WEBHOOK_QUEUE.send(JSON.parse(body))
    );

    return new Response('OK');
  }
};

Troubleshooting

Common Issues

CPU Time Exceeded:

  • Optimize expensive operations
  • Use streams for large data
  • Move processing to Durable Objects
  • Consider splitting work across multiple requests

Memory Exceeded:

  • Stream large responses instead of buffering
  • Clear unused variables
  • Use chunked processing

Script Size Too Large:

  • Remove unused dependencies
  • Use code splitting
  • Minify production builds
  • Check wrangler deploy --dry-run --outdir=dist output

Binding Not Found:

  • Check wrangler.toml configuration
  • Ensure binding name matches env property
  • Redeploy after configuration changes

CORS Errors:

  • Add proper CORS headers
  • Handle OPTIONS requests
  • Check allowed origins
Debugging
bash
# Local debugging
wrangler dev --local

# Remote debugging (real edge environment)
wrangler dev --remote

# Inspect bundle
wrangler deploy --dry-run --outdir=dist

# Check logs
wrangler tail --format pretty

Resources

Implementation Checklist

Initial Setup
  • Install Wrangler CLI
  • Login to Cloudflare account
  • Create new Worker project
  • Configure wrangler.toml
  • Test locally with wrangler dev
Development
  • Implement fetch handler
  • Add error handling
  • Set up TypeScript types
  • Configure bindings (KV, D1, R2, etc.)
  • Add environment variables
  • Implement caching strategy
Testing
  • Write unit tests with Vitest
  • Test locally with wrangler dev
  • Test on remote edge
  • Validate bindings work
Deployment
  • Set up environments (staging, production)
  • Configure routes or custom domains
  • Add secrets
  • Set up CI/CD pipeline
  • Deploy to production
  • Monitor logs and analytics
Production
  • Set up error tracking
  • Configure analytics
  • Implement rate limiting
  • Add monitoring alerts
  • Document API endpoints
  • Set up rollback procedure

© einverne, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in claude/skills/cloudflare-workers of einverne/dotfiles.

Open the folder on GitHubat commit c6c0686

Compare with similar skills

Cloudflare Workers next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Cloudflare Workers compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Cloudflare Workers this skilleinverne/dotfiles121—~8.4kAutomated safety check: PassMIT
AWS Lambda Durable Functionsawslabs/agent-plugins915—~2.3kAutomated safety check: PassApache-2.0
Cloudflare Workers Multi Langsecondsky/claude-skills227—~1.3kAutomated safety check: PassMIT
jscpd Code Migration Trackerkucherenko/jscpd6.4k—~5kAutomated safety check: PassMIT
Cross-Language Coding Standardszereight/gitlab-mcp2k1 repos~1.4kAutomated safety check: PassMIT
Coding Agentmastra-ai/mastra29k—~2.3kAutomated safety check: PassCustom licence

Similar skills

  • AWS Lambda Durable Functions

    awslabs/agent-plugins

    Official

    Build resilient, long-running, multi-step applications with AWS Lambda durable functions with automatic state persistence, retry logic, and orchestration for long-running executions.

    915 GitHub stars~2.3k tokensUpdated 2 days ago
    Backend & APIsAuto-check passed
  • Cloudflare Workers Multi Lang

    secondsky/claude-skills

    Multi-language Workers development with Rust, Python, and WebAssembly.

    227 GitHub stars~1.3k tokensUpdated 10 days ago
    Auto-check passed
  • Measures a code port between languages or frameworks with jscpd's function-level comparison, porting tests before code and tracking what is left unmatched.

    6.4k GitHub stars~5k tokensUpdated today
    DevelopmentAuto-check passed
  • Shared reference for naming, function size, complexity and error handling rules that reviewer agents apply across TypeScript, Python, Go, Rust, Java, C# and Swift.

    2k GitHub starsUsed in 1 repo~1.4k tokens
    DevelopmentAuto-check passed
  • Coding Agent

    mastra-ai/mastra

    Authoring playbook for building agents that write, edit, review, or refactor code.

    29k GitHub stars~2.3k tokensUpdated today
    DevelopmentAuto-check passed
  • Supercov

    supercorp-ai/supercov

    Measures test coverage and code quality in a repository with the supercov CLI, and turns what it finds into small, focused tests or fixes.

    150 GitHub starsUsed in 1 repo~415 tokens
    Testing & QAAuto-check passed

More from einverne/dotfiles

All 39 skills in this repo
  • Chrome Devtools

    einverne/dotfiles

    Browser automation, debugging, and performance analysis using Puppeteer CLI scripts.

    121 GitHub starsUsed in 2 repos~1.6k tokens
    Auto-check: notes
  • DOCX

    einverne/dotfiles

    Comprehensive document creation, editing, and analysis with support for tracked changes, comments, formatting preservation, and text extraction.

    121 GitHub starsUsed in 35 repos~2.5k tokens
    Auto-check: notes
  • PDF

    einverne/dotfiles

    Comprehensive PDF manipulation toolkit for extracting text and tables, creating new PDFs, merging/splitting documents, and handling forms.

    121 GitHub starsUsed in 47 repos~1.8k tokens
    Auto-check passed
  • Gemini Audio

    einverne/dotfiles

    Guide for implementing Google Gemini API audio capabilities - analyze audio with transcription, summarization, and understanding (up to 9.5 hours), plus generate speech with controllable TTS.

    121 GitHub starsUsed in 1 repo~2k tokens
    Auto-check: notes
  • Gemini Image Gen

    einverne/dotfiles

    Guide for implementing Google Gemini API image generation - create high-quality images from text prompts using gemini-2.5-flash-image model.

    121 GitHub starsUsed in 1 repo~1.8k tokens
    Auto-check: notes
  • Gemini Vision

    einverne/dotfiles

    Guide for implementing Google Gemini API image understanding - analyze images with captioning, classification, visual QA, object detection, segmentation, and multi-image comparison.

    121 GitHub starsUsed in 1 repo~1.6k tokens
    Auto-check: notes

Categories

Questions about Cloudflare Workers

What does Cloudflare Workers do?

Comprehensive guide for building serverless applications with Cloudflare Workers. Cloudflare Workers is an agent skill from einverne/dotfiles. Comprehensive guide for building serverless applications with Cloudflare Workers.

When should I use Cloudflare Workers?

Cloudflare Workers fits situations like: developing Workers; configuring bindings; implementing runtime APIs; testing Workers.

How do I install Cloudflare Workers in Claude Code?

Run `npx skills add einverne/dotfiles --skill cloudflare-workers -a claude-code`. Or copy the skill folder (claude/skills/cloudflare-workers in einverne/dotfiles) into .claude/skills/cloudflare-workers in your project. Claude Code loads it when a task matches its description.

How do I install Cloudflare Workers in Codex?

Run `npx skills add einverne/dotfiles --skill cloudflare-workers -a codex`. Or copy the skill folder (claude/skills/cloudflare-workers in einverne/dotfiles) into .agents/skills/cloudflare-workers in your project. Codex loads it when a task matches its description.

Can I use Cloudflare Workers in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add einverne/dotfiles --skill cloudflare-workers -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/cloudflare-workers, .gemini/skills/cloudflare-workers, .github/skills/cloudflare-workers and .opencode/skills/cloudflare-workers in your project.

What does Cloudflare Workers need to run?

Going by SKILL.md and its folder, Cloudflare Workers needs the command-line tools its instructions call (wrangler, npm, npx and curl) and credentials named API_KEY, CLOUDFLARE_API_TOKEN, JWT_SECRET and WEBHOOK_SECRET. Our summary lists: Node.js; A credential in API_KEY.

Does Cloudflare Workers access the network?

SKILL.md names 4 domains. As links in the text: developers.cloudflare.com, discord.cloudflare.com, github.com and cloudflarestatus.com. This is read from the text; nothing was executed.

Is Cloudflare Workers safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Cloudflare Workers use?

Cloudflare Workers is published under the MIT licence (declared in SKILL.md). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Cloudflare Workers use?

About 8.4k tokens (SKILL.md is roughly 34k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Cloudflare Workers?

Skills that share tags, products or a category with Cloudflare Workers: AWS Lambda Durable Functions (awslabs/agent-plugins, 915 stars), Cloudflare Workers Multi Lang (secondsky/claude-skills, 227 stars), jscpd Code Migration Tracker (kucherenko/jscpd, 6.4k stars) and Cross-Language Coding Standards (zereight/gitlab-mcp, 2k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Cloudflare Workers?

einverne (a GitHub user) maintains it in einverne/dotfiles, which has 121 GitHub stars. The repository holds 39 skills in this directory. The repository was last updated on September 9, 2026.

Source: einverne/dotfiles on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.