Getting Started
Deploy your first Cloudflare Worker - request handler, environment, secrets, deploy globally in 30 seconds
Getting Started
The fastest first edge function: Cloudflare Workers. Free tier, single CLI command to deploy, runs in 300+ POPs.
Install Wrangler (the CLI)
npm install -g wrangler
wrangler login # opens a browser to authenticateCreate a Worker
mkdir my-worker && cd my-worker
npm create cloudflare@latest -- --type=hello-worldPick "Hello World example" → "JavaScript" → "Yes" to deploy. The scaffolding looks like:
my-worker/
├── src/
│ └── index.js
├── wrangler.toml
├── package.jsonsrc/index.js:
export default {
async fetch(request, env, ctx) {
return new Response('Hello, edge!', {
headers: { 'content-type': 'text/plain' },
});
},
};Run Locally
wrangler dev
# In another terminal:
curl http://localhost:8787
# Hello, edge!wrangler dev runs the same V8 runtime locally that runs at the edge — fidelity is excellent.
Deploy Globally
wrangler deployIn ~10 seconds your Worker is live in 300+ POPs at https://my-worker.<your-subdomain>.workers.dev.
curl https://my-worker.example.workers.dev
# Hello, edge!Visit that URL from anywhere in the world — the request lands in the nearest POP.
A More Useful Handler
Workers run on Request/Response — Web standard APIs. The pattern is:
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
// Routing
if (url.pathname === '/health') {
return new Response('ok');
}
if (url.pathname === '/api/echo') {
const body = await request.text();
return Response.json({
method: request.method,
path: url.pathname,
headers: Object.fromEntries(request.headers),
body,
});
}
// Pass-through to your origin
const originUrl = new URL(url.pathname, 'https://origin.example.com');
return fetch(originUrl, request);
},
};request.headers, request.cf (Cloudflare-specific request metadata like country, timezone), fetch() for outbound — same APIs as the browser.
Bind Storage and Services
wrangler.toml declares bindings — KV namespaces, Durable Objects, D1 databases, R2 buckets, queues, secrets:
name = "my-worker"
main = "src/index.js"
compatibility_date = "2025-05-21"
# KV namespace
[[kv_namespaces]]
binding = "CACHE"
id = "abc123..."
# D1 database (SQLite at the edge)
[[d1_databases]]
binding = "DB"
database_name = "myapp"
database_id = "xyz789..."
# R2 bucket (S3-compatible)
[[r2_buckets]]
binding = "ASSETS"
bucket_name = "my-assets"
# Secrets — set via `wrangler secret put`, not in this fileBindings become properties on the env parameter:
export default {
async fetch(request, env, ctx) {
// KV
const cached = await env.CACHE.get('key');
await env.CACHE.put('key', 'value', { expirationTtl: 300 });
// D1
const { results } = await env.DB.prepare(
'SELECT * FROM users WHERE id = ?'
).bind(42).all();
// R2
const file = await env.ASSETS.get('photo.jpg');
return Response.json({ cached, users: results });
},
};Secrets
Plaintext config goes in wrangler.toml; secrets are set separately:
echo "my-api-key" | wrangler secret put MY_API_KEY
# now env.MY_API_KEY is available at runtimeSecrets are encrypted at rest and never appear in logs.
Trigger by Schedule
Cron-like triggers:
[triggers]
crons = ["0 6 * * *"] # daily at 06:00 UTCexport default {
async scheduled(event, env, ctx) {
await env.CACHE.put('last-run', new Date().toISOString());
},
async fetch(request, env, ctx) { /* ... */ },
};Custom Domains
Workers default to *.workers.dev. To bind to your own domain:
- Add your domain to Cloudflare (DNS).
- In
wrangler.toml:
routes = [
{ pattern = "api.example.com/*", custom_domain = true },
]wrangler deploy.
Now api.example.com/* is handled by your Worker.
Limits to Know
| Limit | Free plan | Paid plan |
|---|---|---|
| CPU time | 10 ms/request | 30 s/request |
| Memory | 128 MB | 128 MB |
| Subrequests (fetch from worker) | 50 | 1000 |
| Script size | 1 MB | 10 MB |
| KV operations | 100k reads/day | Per-operation pricing |
| D1 storage / queries | Generous free tier | Per-operation pricing |
10 ms CPU per request is plenty for routing, auth, header rewrites — not enough for heavy computation. That's by design.
Other Edge Platforms
The same handler shape works on:
- Vercel Edge Functions —
export default async function (request) { ... }inapp/api/route.tswithexport const runtime = 'edge'. - Deno Deploy — top-level
Deno.serve()oraddEventListener('fetch', ...). - Netlify Edge Functions — same as Deno Deploy.
- Fastly Compute@Edge — Rust / JS / AssemblyScript; more involved tooling, faster.
Different deploy commands, similar code.
What's Next
You can write and deploy an edge function. Next:
- Patterns — auth, A/B testing, geo-routing, edge-side rendering, KV storage
- Best Practices — limits, observability, secret management, cost