Cloudflare Integration Guide

Technology: cloudflare · Category: hosting · Last reviewed: 2026-08-23

Source: https://tech-stack.codeamanilabs.org/guide/cloudflare

Insight:

Cloudflare is the edge platform — Workers, D1, KV, and R2 (which serves this dashboard's thumbnails). R2 has zero egress fees, making it the cheap choice for serving images/assets to a bandwidth-constrained African audience. Keep API tokens scoped and server-side.

 ██████╗██╗      ██████╗ ██╗   ██╗██████╗ ███████╗██╗      █████╗ ██████╗ ███████╗
██╔════╝██║     ██╔═══██╗██║   ██║██╔══██╗██╔════╝██║     ██╔══██╗██╔══██╗██╔════╝
██║     ██║     ██║   ██║██║   ██║██║  ██║█████╗  ██║     ███████║██████╔╝█████╗
██║     ██║     ██║   ██║██║   ██║██║  ██║██╔══╝  ██║     ██╔══██║██╔══██╗██╔══╝
╚██████╗███████╗╚██████╔╝╚██████╔╝██████╔╝██║     ███████╗██║  ██║██║  ██║███████╗
 ╚═════╝╚══════╝ ╚═════╝  ╚═════╝ ╚═════╝ ╚═╝     ╚══════╝╚═╝  ╚═╝╚═╝  ╚═╝╚══════╝

Cloudflare Integration Guide

Focus: Building, deploying, and managing Cloudflare Workers, D1, KV, R2, and the full Cloudflare platform from Claude Code using the official MCP server and Wrangler CLI.

Overview

Cloudflare's developer platform offers Workers (serverless), D1 (SQLite at the edge), KV (key-value), R2 (object storage), Durable Objects, Queues, Hyperdrive, Pages, and 2,500+ API endpoints. Claude Code integrates through Cloudflare's remote MCP servers (*.mcp.cloudflare.com, installed via the cloudflare/skills plugin) and the wrangler CLI. Work from the root of your Workers project — Claude Code reads wrangler.jsonc to understand your bindings automatically.

Here is the big picture — a single request hits the edge, runs your Worker, and reaches whichever bindings it needs:

flowchart LR
  U["User request"] --> E["Cloudflare edge"]
  E --> W["Worker<br/>fetch handler"]
  W --> D1["D1<br/>SQLite at edge"]
  W --> KV["KV<br/>key-value cache"]
  W --> R2["R2<br/>object storage"]
  W --> RESP["Response to user"]

Official Documentation

Resource URL
Cloudflare Developers https://developers.cloudflare.com
Claude Code + Cloudflare https://developers.cloudflare.com/agent-setup/claude-code/
Cloudflare's own MCP servers https://developers.cloudflare.com/agents/model-context-protocol/cloudflare/servers-for-cloudflare/
Wrangler CLI https://developers.cloudflare.com/workers/wrangler/
Workers Docs https://developers.cloudflare.com/workers/
Workers Best Practices https://developers.cloudflare.com/workers/best-practices/workers-best-practices/
Workers limits https://developers.cloudflare.com/workers/platform/limits/
D1 Docs https://developers.cloudflare.com/d1/
R2 Docs https://developers.cloudflare.com/r2/

MCP Server Setup

This changed in 2026. Cloudflare no longer ships a single local npx server. The old @cloudflare/mcp-server-cloudflare package is legacy; the Cloudflare API server now lives at github.com/cloudflare/mcp. Today Cloudflare runs a catalog of managed remote MCP servers you connect to over OAuth.

Remote MCP servers

Every server is a hosted HTTPS endpoint under *.mcp.cloudflare.com. Use the Streamable HTTP endpoint at /mcp for new connections (the older /sse URL stays only as an alias — the deprecated HTTP+SSE transport is gone). Authorize with OAuth on first connect.

Server Streamable HTTP endpoint What it does
Cloudflare API ("Code Mode") https://mcp.cloudflare.com/mcp search() + execute() over 2,500+ API endpoints
Documentation https://docs.mcp.cloudflare.com/mcp Search the Cloudflare docs
Workers Bindings https://bindings.mcp.cloudflare.com/mcp Create/list Workers, KV, R2, D1, Hyperdrive
Workers Builds https://builds.mcp.cloudflare.com/mcp Inspect Workers Builds CI runs + logs
Observability https://observability.mcp.cloudflare.com/mcp Query Workers logs, traces, metrics
Radar https://radar.mcp.cloudflare.com/mcp Internet traffic + URL analysis
AI Gateway https://ai-gateway.mcp.cloudflare.com/mcp AI Gateway logs + config
Logpush https://logs.mcp.cloudflare.com/mcp Logpush job health
GraphQL https://graphql.mcp.cloudflare.com/mcp Query the Cloudflare GraphQL analytics API

(Full catalog — Container, Browser Run, AI Search/AutoRAG, Audit Logs, DNS Analytics, DEX, CASB — at the "Cloudflare's own MCP servers" doc above.)

Connect from Claude Code

The recommended path is the Cloudflare Skills plugin, which bundles the MCP servers with contextual skills and slash commands. Run inside Claude Code:

/plugin marketplace add cloudflare/skills
/plugin install cloudflare@cloudflare

Then verify the servers registered:

claude mcp list

To add a single remote server directly instead (Streamable HTTP transport):

claude mcp add --transport http cloudflare-bindings https://bindings.mcp.cloudflare.com/mcp

First use opens an OAuth browser flow. In CI (no browser), skip OAuth by passing a scoped Cloudflare API token as a bearer token — keep it in the environment manager, never in the repo (see ENV_MASTER.md).

Representative tools (Workers Bindings server)

The domain servers expose named tools; the Cloudflare API server exposes just search/execute (Code Mode). Common Workers Bindings tools:

Tool Description
workers_list List all Workers scripts
workers_get_worker_code Fetch Worker source
d1_databases_list / d1_database_query List D1 databases / run SQL
kv_namespaces_list List KV namespaces
r2_buckets_list / r2_bucket_create List / create R2 buckets
hyperdrive_configs_list List Hyperdrive configs

The Cloudflare MCP servers manage Workers/KV/R2/D1/Hyperdrive but cannot edit DNS and cannot upload R2 objects — use a scoped DNS token for DNS and wrangler r2 object put / the S3 API for object uploads (see the credentials table below).


CLI Integration (Wrangler)

Installation

npm install -g wrangler   # Wrangler 4.x is current (v4.125+); needs Node.js 20+

Authentication

# Interactive OAuth login
wrangler login

# Use API token (for CI)
export CLOUDFLARE_API_TOKEN=...

Key Commands

# Create a new Worker project
npm create cloudflare@latest my-worker -- --type worker

# Local development (with hot reload)
wrangler dev

# Deploy to Cloudflare
wrangler deploy

# View production logs (live tail)
wrangler tail my-worker

# D1 database commands
wrangler d1 create my-database
wrangler d1 execute my-database --command "CREATE TABLE users (id INTEGER PRIMARY KEY)"
wrangler d1 execute my-database --file schema.sql
wrangler d1 migrations apply my-database --local
wrangler d1 migrations apply my-database --remote

# KV namespace commands (v3.60+ uses a SPACE, not a colon; kv:namespace is deprecated)
wrangler kv namespace create MY_NAMESPACE
wrangler kv key put --binding=MY_NAMESPACE "key" "value"          # add --remote to write to production
wrangler kv key get --binding=MY_NAMESPACE "key"

# R2 bucket commands
wrangler r2 bucket create my-bucket
wrangler r2 object put my-bucket/path/to/file.json --file ./data.json

# Pages deployment
wrangler pages deploy dist/ --project-name my-site

# Secret management
wrangler secret put MY_SECRET
wrangler secret list

Worker Example

src/index.ts:

export interface Env {
  DB: D1Database;
  KV: KVNamespace;
  MY_SECRET: string;
}

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

    if (url.pathname === "/users") {
      const { results } = await env.DB.prepare(
        "SELECT * FROM users ORDER BY created_at DESC LIMIT 10"
      ).all();
      return Response.json(results);
    }

    if (url.pathname === "/kv") {
      const value = await env.KV.get("my-key");
      return new Response(value ?? "not found");
    }

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

wrangler.jsonc:

{
  "name": "my-worker",
  "main": "src/index.ts",
  // Set this to today's date when you start a project, then bump deliberately.
  "compatibility_date": "2026-08-23",
  "d1_databases": [
    { "binding": "DB", "database_name": "my-database", "database_id": "..." }
  ],
  "kv_namespaces": [
    { "binding": "KV", "id": "..." }
  ]
}

Node.js compat is now on by default. For compatibility_date of 2026-08-04 or later, nodejs_compat (and nodejs_compat_v2) are enabled automatically — node:crypto, node:buffer, node:stream, etc. and npm packages that use them work with no flag. Only older compat dates still need "compatibility_flags": ["nodejs_compat"]. Prefer generating your Env type with wrangler types over hand-writing it, so config and types can't drift.


Bindings reference

Verified against developers.cloudflare.com/workers/wrangler/configuration. A binding is a direct, in-process handle to a Cloudflare resource on env — no network hop, no auth token. Best practice is to use bindings over REST APIs.

Resource wrangler.jsonc key Runtime type on env Create with
KV kv_namespaces: [{ binding, id }] KVNamespace wrangler kv namespace create
R2 r2_buckets: [{ binding, bucket_name }] R2Bucket wrangler r2 bucket create
D1 d1_databases: [{ binding, database_name, database_id }] D1Database wrangler d1 create
Durable Objects durable_objects.bindings: [{ name, class_name }] + migrations your DO class namespace declare class + migration
Queues (producer) queues.producers: [{ binding, queue }] Queue<T> wrangler queues create
Queues (consumer) queues.consumers: [{ queue, max_batch_size, dead_letter_queue }] queue() handler (bound on the consumer Worker)
Hyperdrive hyperdrive: [{ binding, id }] Hyperdrive wrangler hyperdrive create
// Durable Objects need a migration the first time a class is added.
{
  "durable_objects": { "bindings": [{ "name": "COUNTER", "class_name": "Counter" }] },
  "migrations": [{ "tag": "v1", "new_sqlite_classes": ["Counter"] }],
  "queues": {
    "producers": [{ "binding": "JOBS", "queue": "jobs" }],
    "consumers": [{ "queue": "jobs", "max_batch_size": 10, "dead_letter_queue": "jobs-dlq" }]
  },
  "hyperdrive": [{ "binding": "HYPERDRIVE", "id": "<config-id>" }]
}

Pages & Functions

Verified against Cloudflare's official docs (developers.cloudflare.com/pages/functions). Pages serves your static build; Pages Functions add server-side code on the same deploy — file-based routing out of a functions/ directory, running on Workers.

Pages is two layers in one deploy: static assets plus an optional functions/ directory that Cloudflare compiles into a single Worker. Files map to URL paths automatically:

flowchart TD
  REQ["Incoming request"] --> RT["_routes.json<br/>include · exclude"]
  RT -->|"excluded path"| ASSET["Static asset<br/>from build dir"]
  RT -->|"included path"| FN["functions/ dir<br/>file-based routing"]
  FN --> H["onRequest handler<br/>EventContext"]
  H --> ENV["env bindings<br/>KV · D1 · R2"]
  H --> RESP["Response"]
  ASSET --> RESP

File-based routing

A file's path under functions/ becomes its route:

File Route
functions/index.ts /
functions/api/hello.ts /api/hello
functions/users/[user].ts /users/:user (single segment → params.user string)
functions/api/[[path]].ts /api/* (catch-all → params.path array)

More specific routes (fewer wildcards) win over catch-alls.

Pages Function example

A catch-all API handler at functions/api/[[path]].ts. Each onRequest (or method-specific onRequestGet / onRequestPost) receives an EventContext with request, env, params, waitUntil, next, and data. The PagesFunction<Env> generic types your bindings:

interface Env {
  KV: KVNamespace;
  DB: D1Database;
}

// Handles GET /api/anything/here
export const onRequestGet: PagesFunction<Env> = async (context) => {
  const { params, env } = context;
  // params.path is the segments after /api/ as a string[]
  const segments = params.path as string[];

  if (segments[0] === "ping") {
    return Response.json({ ok: true, ts: Date.now() });
  }

  const cached = await env.KV.get(segments.join("/"));
  return cached
    ? new Response(cached)
    : new Response("Not Found", { status: 404 });
};

// A bare onRequest runs for any verb without a more specific onRequestVerb export.
export const onRequest: PagesFunction<Env> = async ({ next }) => {
  return next(); // fall through to the static asset server
};

Deploy

# Build your site, then deploy the output directory (Functions in ./functions are bundled)
wrangler pages deploy dist/ --project-name my-site

# Local dev with Functions + bindings emulated
wrangler pages dev dist/

Bindings

Pages Functions read bindings off context.env, same as Workers. Configure them in wrangler.jsonc (or the Pages project's dashboard Settings → Bindings for production/preview). Keep compatibility_date current.

{
  "name": "my-site",
  "pages_build_output_dir": "dist",
  "compatibility_date": "2026-08-23",
  "kv_namespaces": [
    { "binding": "KV", "id": "..." }
  ],
  "d1_databases": [
    { "binding": "DB", "database_name": "my-database", "database_id": "..." }
  ]
}

_routes.json

Cloudflare auto-generates this, but you can ship your own at the build-output root to control which paths invoke Functions (vs. serving a static asset directly). exclude takes priority over include; wildcards match any number of segments:

{
  "version": 1,
  "include": ["/api/*"],
  "exclude": ["/api/static/*"]
}

Gotcha: if a path matches no include rule (or hits an exclude), the request is served as a static asset and your Function never runs — a silent 404/wrong-content instead of an error. When an API route mysteriously bypasses your handler, check _routes.json first. Run wrangler pages deploy to regenerate the auto version.


Environment Variables

# Required
CLOUDFLARE_API_TOKEN=...         # From dash.cloudflare.com → Profile → API Tokens
CLOUDFLARE_ACCOUNT_ID=...        # From dash.cloudflare.com (right sidebar)

# Wrangler picks these up automatically from environment
# Or use: wrangler secret put MY_SECRET for runtime secrets

Automation Workflows

Claude Code Hook: Auto-deploy on Save

.claude/settings.json:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "command": "if echo \"$CLAUDE_FILE_PATH\" | grep -q 'src/'; then echo 'Worker source changed — run wrangler deploy to push'; fi"
          }
        ]
      }
    ]
  }
}

Slash Command: Deploy and Tail Logs

.claude/commands/cf-deploy.md:

Deploy the current Cloudflare Worker and confirm it's live.

1. Use Bash to run `wrangler deploy` and capture the deployed URL
2. Use Bash to run `wrangler tail --format pretty` for 10 seconds to check for errors
3. Report the deployed Worker URL and any runtime errors observed

Usage: /project:cf-deploy

GitHub Actions: CI Deploy to Cloudflare Workers

# .github/workflows/cloudflare.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: '22' }
      - run: npm ci
      - name: Run D1 migrations
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
          CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
        run: npx wrangler d1 migrations apply my-database --remote
      - name: Deploy Worker
        uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CF_API_TOKEN }}
          accountId: ${{ secrets.CF_ACCOUNT_ID }}

R2: Create Buckets & API Tokens (step-by-step)

Verified against Cloudflare's official docs (developers.cloudflare.com/r2). Bucket names: lowercase letters, numbers, hyphens only.

You have three clean paths into R2 — pick the one that matches the job, and serving objects publicly is just as straightforward:

flowchart TD
  CC["Claude Code"] --> WR["Wrangler<br/>r2 object put"]
  CC --> S3["S3 API<br/>Access Key + Secret"]
  CC --> MCP["Cloudflare MCP<br/>create-list buckets"]
  WR --> B["R2 bucket"]
  S3 --> B
  MCP --> B
  B --> CD["Custom domain"]
  B --> RD["r2.dev URL"]
  CD --> IMG["world-readable img on frontend"]
  RD --> IMG

Create a bucket

Dashboard — open R2 → Overview

  1. Go to R2 object storage → Overview.
  2. Select Create bucket.
  3. Enter a name, pick a location + default storage class.
  4. Select Create bucket.

Wrangler (auth via wrangler login, no keys needed):

npx wrangler r2 bucket create my-bucket
npx wrangler r2 bucket list

REST API (needs an API token with R2 edit — see below):

curl https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/r2/buckets \
  -H "Authorization: Bearer $R2_ADMIN_TOKEN" -H "Content-Type: application/json" \
  --data '{"name":"my-bucket"}'

Get R2 API tokens (S3 Access Key ID + Secret)

Needed for S3 SDKs (boto3, AWS SDK, rclone) and for an app to read/write objects. Wrangler does not need these.

Dashboard — open R2 API tokens

  1. R2 → Overview → under Account details, select Manage next to API Tokens.
  2. Choose Create Account API token (tied to the account, survives user removal — best for automation) or Create User API token (tied to your user).
  3. Under Permissions pick one: Object Read & Write (typical), Object Read, Admin Read & Write, or Admin Read.
  4. Select Apply to specific buckets only and choose your bucket (least privilege).
  5. Create API Token, then copy the Access Key ID + Secret Access Key now — the secret is shown only once.
  6. Your S3 endpoint is https://<ACCOUNT_ID>.r2.cloudflarestorage.com.

Deriving S3 creds from any Cloudflare API token: Access Key ID = the token's id; Secret Access Key = the SHA‑256 hash of the token value.

Serve objects publicly (for <img> on the frontend)

Credentials & Permissions for Claude Code automation

What Claude Code needs to automate Cloudflare, and the gotchas that block it:

Credential Create at Scope / permission Lets Claude Code automate
Account ID R2 Overview / dash URL identifier (not secret) target API + S3 endpoint
Zone ID domain → Overview (API section) identifier (not secret) DNS API calls for that zone
R2 S3 token (Access Key + Secret) R2 → Manage R2 API Tokens Object Read & Write, scoped to a bucket upload/serve objects (boto3, AWS SDK, rclone, app proxy)
R2 admin token Account API Tokens → Custom Workers R2 Storage: Edit create/list/delete buckets + settings via API
DNS token Account API Tokens → Custom Zone → DNS → Edit (+ Zone → Read) add/edit DNS records (subdomains, R2 custom domains)
Global API Key My Profile → API Tokens full account (legacy) everything via wrangler legacy auth
OAuth (wrangler login) local browser your user's permissions all local wrangler commands

Automation gotchas (learned the hard way):


Common Use Cases

Use Case Approach
Edge API with D1 Worker + wrangler d1 execute for schema
Global KV cache KVNamespace binding in Worker
Static site wrangler pages deploy dist/
File storage R2 bucket + Worker presigned URLs
Background jobs / fan-out Queues producer + separate consumer Worker
Query an existing Postgres/MySQL DB Hyperdrive binding (connection pooling + cache)
Stateful coordination / counters Durable Objects (SQLite storage)
Rate limiting Cloudflare Rate Limiting via the API MCP execute
DNS management scoped DNS token (not the MCP — it can't edit DNS)

Troubleshooting

Issue Fix
CLOUDFLARE_API_TOKEN missing Create token at dash.cloudflare.com with Workers:Edit permissions
wrangler dev port conflict Use wrangler dev --port 8788
D1 migration not applying Run wrangler d1 migrations list my-database --remote to check state
Worker over size limit Limit is 3 MB (Free) / 10 MB (Paid) after gzip — minify (on by default in v4), trim deps, or split into sub-workers
kv:namespace command errors The colon form is deprecated — use a space: wrangler kv namespace ..., wrangler kv key ...
Exceeded CPU time (Error 1102) Free CPU is 10 ms; on Paid raise limits.cpu_ms (default 30 s, max 5 min) or offload to Queues/Durable Objects
KV stale reads KV is eventually consistent; use D1 or Durable Objects for strong consistency

Official docs: