cheat sheet
wrangler
Package-level reference for the wrangler CLI on npm — install, Node support, transitive dependency footprint, and platform-binding gotchas.
wrangler
What it is
wrangler is Cloudflare's official command-line tool for building, configuring, and deploying everything on the Workers platform — Workers, Pages, Durable Objects, KV, R2, D1, Vectorize, Hyperdrive, Queues, Workflows, Workers AI, and Secrets Store. It is the single entry point that talks to the Cloudflare API on your behalf and runs a local emulator (Miniflare) for offline development.
Reach for wrangler whenever you're touching Cloudflare's edge platform — there is no second-party alternative. For multi-cloud deploys, pair it with provider-specific CLIs (vercel, netlify, aws, flyctl) and orchestrate via your CI.
Install
Wrangler is a CLI. The Cloudflare team recommends installing it as a dev-dependency per project rather than globally — it pins the version alongside your code.
npm install -D wrangler
Output: added wrangler to devDependencies
npx wrangler --version
Output: prints installed wrangler version
pnpm add -D wrangler
Output: added 1 package, linked from store
yarn add -D wrangler
Output: added wrangler to devDependencies
bun add -d wrangler
Output: installed wrangler as a dev dependency
For global use:
npm install -g wrangler
Output: added wrangler globally; wrangler on PATH
Versioning & Node support
Current line is wrangler@4.x (released 2025). Cloudflare publishes new minors weekly tracking platform features.
- Node 20+ required on the
4.xline.3.xsupported Node 16+. - Dual ESM/CJS for the published code, but the CLI itself is ESM and uses top-level await.
- TypeScript types ship in-tree for the
unstable_devprogrammatic API. - Semver is followed but breaking changes are frequent because the underlying platform evolves quickly — expect to upgrade at least once per quarter.
Package metadata
- Maintainer: Cloudflare (
cloudflare/workers-sdkmono-repo on GitHub) - Project home: github.com/cloudflare/workers-sdk
- Docs: developers.cloudflare.com/workers/wrangler
- npm: npmjs.com/package/wrangler
- License: MIT OR Apache-2.0
- First released: 2019 (the original Rust-based
wranglerwas rewritten in TypeScript ~2022) - Downloads: ~1 million weekly downloads — dominant CLI for Workers, growing with the platform.
Peer dependencies & extras
wrangler declares no peer-deps but pulls in a massive transitive tree:
miniflare— local Workers emulator (V8 isolates via workerd)esbuild— bundles your Worker sourcechokidar,chokidar-cli— file-watchingselfsigned,xxhash-wasm,unenv,path-to-regexp, plus dozens of utilities
Expect ~200–300 MB in node_modules/wrangler/ plus its deps after install. Pin via your lockfile because Cloudflare ships frequently.
Companion packages:
miniflare— usable standalone for Vitest / Jest integration tests against a Workers runtime@cloudflare/workers-types— TypeScript types for the runtime bindings (Env, ExecutionContext, KV, R2, etc.)@cloudflare/vitest-pool-workers— run tests inside a real Workers isolatec3(create-cloudflare) —npm create cloudflare@latestscaffolder; uses wrangler internally
Alternatives
There is no second-party alternative for the Workers platform itself — only Cloudflare can deploy to their edge. The closest comparisons are deploy-CLIs for other edge/serverless platforms:
| Tool | Platform |
|---|---|
vercel | Vercel — Functions, Edge Functions, Cron, KV, Blob, Postgres |
netlify-cli | Netlify — Functions, Edge Functions, Forms |
aws-cdk / sst | AWS Lambda + edge via CloudFront |
flyctl | Fly.io — full machines + edge regions |
deno deploy | Deno Deploy — Deno-native edge runtime |
firebase | Firebase Functions, Hosting |
For multi-cloud workflows, the OpenNext and Hono ecosystems publish adapters that target wrangler + competitors from the same source.
Common gotchas
- KV-namespace-id mismatch across envs. Each environment (
[env.production],[env.staging]) declares its own[[kv_namespaces]]with a unique ID. Copy-pasting between environments without rotating IDs leaks data between staging and prod. Always runwrangler kv namespace listto verify. - D1 migration drift.
wrangler d1 migrations applyruns migrations in./migrations/. Locally and remotely they tick independently — running locally without applying remotely (or vice versa) silently desyncs. Usewrangler d1 migrations list <db>against each env before deploying. wrangler devvswrangler dev --remote. The default is the local Miniflare emulator.--remoteproxies to the real edge so KV/R2/D1 hit production data — convenient but irreversible. Always confirm which env you're targeting before destructive ops.- Pages auto-deploy can race with manual
wrangler pages deploy. If GitHub-connected Pages and a manual deploy both run, the last one wins — and that may not be the commit you wanted. Either disable the auto-deploy integration or rely on it exclusively. - Secrets are NOT in
wrangler.toml. Usewrangler secret put NAMEto store secrets, OR commit a (gitignored).dev.varsfile for local-only secrets. Putting secrets in[vars]writes them to your tracked TOML and leaks them on GitHub. compatibility_dateis sticky. Setting it too far in the past skips runtime features (Web Crypto, Streams API patches). Setting it too far in the future opts into experimental behaviour. Refresh it on every quarterly upgrade.unenv/ Node compat is partial.nodejs_compatflag enables a subset ofnode:*modules.fs,child_process, native addons, anything depending on event-loop introspection — not supported. Audit your dependency tree before assuming a library "works on Workers".- Account/zone scoping confusion.
wrangler whoamishows your token's account. Multi-account setups needCLOUDFLARE_ACCOUNT_IDin env or the--account-idflag, otherwise deploys land in the wrong account silently.
Real-world recipes
These recipes target deploy-time setup — bindings, environments, and secret rotation — rather than runtime API usage. Refer to the companion article for the runtime side.
Multi-env Worker with KV, R2, D1, and Queues
A single Worker that uses every common binding type, split between production and staging. The wrangler.jsonc (or wrangler.toml) configuration:
{
"name": "my-app",
"main": "src/index.ts",
"compatibility_date": "2026-05-01",
"compatibility_flags": ["nodejs_compat"],
"vars": { "APP_NAME": "my-app" },
"kv_namespaces": [
{ "binding": "CACHE", "id": "REPLACE_WITH_PROD_ID", "preview_id": "REPLACE_WITH_PREVIEW_ID" }
],
"r2_buckets": [
{ "binding": "UPLOADS", "bucket_name": "my-app-uploads" }
],
"d1_databases": [
{ "binding": "DB", "database_name": "my-app-prod", "database_id": "REPLACE_WITH_D1_ID" }
],
"queues": {
"producers": [{ "binding": "EVENTS", "queue": "my-app-events" }],
"consumers": [{ "queue": "my-app-events", "max_batch_size": 10 }]
},
"env": {
"staging": {
"vars": { "APP_NAME": "my-app-staging" },
"kv_namespaces": [{ "binding": "CACHE", "id": "REPLACE_WITH_STAGING_KV_ID" }],
"r2_buckets": [{ "binding": "UPLOADS", "bucket_name": "my-app-uploads-staging" }],
"d1_databases": [{ "binding": "DB", "database_name": "my-app-staging", "database_id": "REPLACE_WITH_STAGING_D1_ID" }]
}
}
}
Create the resources, capture the IDs, paste them in:
wrangler kv namespace create CACHE
wrangler kv namespace create CACHE --preview
wrangler r2 bucket create my-app-uploads
wrangler d1 create my-app-prod
wrangler queues create my-app-events
Output: each command prints the ID/binding line you copy into the config.
Deploy each env:
wrangler deploy # production (top-level config)
wrangler deploy --env staging # staging block
Output: two separate Workers — my-app and my-app-staging — each with its own bindings.
Secret rotation workflow
Cloudflare secrets are write-only — wrangler secret list shows names but not values. A safe rotation looks like:
wrangler secret put DATABASE_URL --env staging # write new value to staging first
wrangler tail --env staging # verify a request succeeds
wrangler secret put DATABASE_URL --env production # rotate prod
wrangler secret delete OLD_DATABASE_URL --env production # cleanup if you renamed
Output: each secret put prompts for the value via stdin (or accepts via --input-file).
For automated rotation in CI:
- name: Rotate API key
run: echo "$NEW_KEY" | wrangler secret put API_KEY --env production
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
NEW_KEY: ${{ secrets.NEW_API_KEY }}
Output: secret rotated without revealing the value in logs.
Local dev with .dev.vars
wrangler dev reads .dev.vars for local-only secrets. It does NOT upload to production — strictly a local-dev convention.
# .dev.vars (gitignored)
DATABASE_URL="postgres://localhost/dev"
STRIPE_SECRET="sk_test_..."
wrangler dev
Output: local Worker has env.DATABASE_URL and env.STRIPE_SECRET injected; production secrets stay untouched.
Add to .gitignore: .dev.vars.
Promoting a KV namespace from preview to production
The recommended flow is two separate namespaces from day one, but if you started with one and need to split:
wrangler kv namespace create CACHE_PROD # creates a new namespace
wrangler kv key list --binding CACHE --preview > preview-keys.json
# transfer values via a script that reads and writes
wrangler kv key put --binding CACHE_PROD "key" "value"
Output: (none — exits 0 on success)
For bulk transfers, write a small Worker that reads from one namespace and writes to another — it runs much faster than CLI roundtrips.
wrangler pages deploy for static sites
For sites built outside the connected-Git flow (or with custom build pipelines), upload dist/ directly:
wrangler pages deploy ./dist --project-name=my-site --branch=main
Output: deploys to my-site.pages.dev; subsequent deploys to the same branch overwrite the latest.
For preview deploys per PR:
wrangler pages deploy ./dist --project-name=my-site --branch=$GITHUB_HEAD_REF
Output: branch-scoped preview URL my-site-<branch>.pages.dev.
Production deployment
Cloudflare Workers / Pages is itself a production deploy target. The "deployment" concern here is wrangler's role in delivering code safely.
Preview vs production environments
The [env.<name>] block in config defines a fully independent Worker — separate route(s), separate bindings, separate secrets. The pattern most teams settle on:
- default (no env) = production
[env.staging]= staging on a.workers.devsubdomain[env.preview]= ephemeral preview, sometimes per-branch
Build the deploy command into npm scripts:
{
"scripts": {
"deploy": "wrangler deploy",
"deploy:staging": "wrangler deploy --env staging",
"deploy:preview": "wrangler deploy --env preview --name preview-$BRANCH"
}
}
CI deploy with API tokens
Use a scoped API token (Cloudflare dashboard → My Profile → API Tokens) instead of a global key. Scope to one account, one Workers Scripts edit permission.
- run: npm ci
- run: npm run deploy
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
The token-scoped approach is preferred over wrangler login in CI — interactive OAuth doesn't work in headless environments.
Rolling back
A previous deploy of a Worker is reachable by tag and rollback is a one-liner:
wrangler deployments list # see recent deploys with IDs
wrangler rollback <deploy-id> # restore that version
Output: the previous code is live within seconds; bindings and secrets are unchanged.
For Pages, rollback through the dashboard or via the CLI:
wrangler pages deployment list --project-name=my-site
wrangler pages deployment tail <id> # inspect a specific deploy
Output: (none — exits 0 on success)
Observability after deploy
wrangler tail streams logs from the live Worker — invaluable for debugging the moments after a deploy.
wrangler tail --env production --format=pretty
Output: real-time log lines for incoming requests; Ctrl-C to stop.
For long-term observability, enable Workers Analytics Engine, Logpush, or a third-party APM like Sentry / Honeycomb (via the OpenTelemetry exporter).
Version migration guide
Wrangler ships rapidly — minors weekly, majors yearly. Pin in package.json and upgrade deliberately. Check the official changelog for breaking changes specific to your version, since the platform evolves quickly.
| From | To | Likely changes (verify against the release notes) |
|---|---|---|
wrangler@2 | wrangler@3 | TypeScript rewrite (the original was Rust). Config format unchanged. Some legacy CLI flags removed. |
wrangler@3 | wrangler@4 | Node 20+ floor. wrangler.toml → wrangler.jsonc recommended (TOML still supported). Tightened deploy permissions in tokens. New observability defaults. |
| Any major | next | Compatibility flags policy, unenv Node compat improvements, new binding types, and dashboard-managed deploy options shift frequently. |
Recommended upgrade flow:
- Read the release notes for every major between current and target. Cloudflare publishes them on the workers-sdk repo.
- Bump in a branch:
npm install -D wrangler@<target>. - Run
wrangler deploy --dry-run --outdir distto surface config-parsing errors without deploying. - Run
wrangler devand exercise every binding locally. - Deploy to staging first, run smoke tests, then promote.
- Refresh
compatibility_dateat the same time — many breaking runtime behaviours are gated on it, not the wrangler version.
wrangler.toml → wrangler.jsonc: the JSONC format supports comments, is easier to programmatically generate, and aligns with the broader Cloudflare-platform tooling. The TOML format is still supported but new examples in the docs default to JSONC.
ESM/CJS interop & bundling
Wrangler is a CLI; the interop concerns are about the Worker code it bundles, not wrangler itself.
| Concern | Pattern |
|---|---|
| Worker module format | Modules-format Workers are ESM (export default { fetch(...) }). Service Workers (addEventListener("fetch", ...)) are legacy and discouraged. |
| Source language | TypeScript and JavaScript both work. wrangler bundles via esbuild. |
| Node-compat | Set compatibility_flags = ["nodejs_compat"] for node:* imports. Not all modules supported — fs, child_process, native addons, etc., are out. |
| CommonJS deps | esbuild converts CJS to ESM at bundle time. Most npm packages "just work". Some that rely on __dirname, require.resolve, or dynamic require break — audit. |
| Wasm modules | Import with import wasm from "./module.wasm"; then WebAssembly.instantiate(wasm). wrangler bundles the binary into the Worker upload. |
unstable_dev programmatic API | import { unstable_dev } from "wrangler" lets you launch a Worker inside a Node process for tests. Stable enough for CI; signature may change between minors. |
| Bundle size limit | Free tier: 1 MB compressed. Paid: 10 MB. wrangler reports the size on deploy; over-limit fails the upload. |
| Tree-shaking | esbuild tree-shakes ESM imports automatically. CJS deps are harder to shake — prefer ESM-published packages where possible. |
Plugin & ecosystem coverage
Wrangler is the single CLI; the "ecosystem" is the binding catalog plus a small set of dev / testing companions.
| Binding / feature | Purpose |
|---|---|
| Workers KV | Eventually-consistent global key-value store. Low write throughput, fast global reads. |
| R2 | S3-compatible object storage. Zero egress fees. |
| D1 | SQLite-on-the-edge. Read replicas globally, primary in one region. |
| Durable Objects | Single-instance globally-coordinated stateful objects with SQLite storage and WebSocket support. |
| Hyperdrive | Connection pool for external Postgres / MySQL — caches + accelerates origin DB. |
| Queues | Producer/consumer messaging with batching, retries, dead-letter queues. |
| Workflows | Durable execution engine — long-running step-based workflows with sleep + retry. |
| Workers AI | LLM + image + speech models via env.AI.run(model, input). |
| Vectorize | Vector database for RAG / semantic search. |
| Browser Rendering | Headless Chrome via @cloudflare/puppeteer. |
| Email Sending | Outbound transactional email via Email Workers. |
| Email Routing | Inbound email → Worker (handler receives a message). |
| Containers | Run Docker containers alongside Workers (newer offering — verify availability for your account tier). |
| Service Bindings | Worker-to-Worker calls without external HTTP. |
| Logpush | Push request / error logs to S3, GCS, R2, Datadog. |
| Companion package | Purpose |
|---|---|
miniflare | Workers runtime emulator. Used internally by wrangler dev; also standalone for tests. |
@cloudflare/workers-types | TypeScript types for runtime bindings. Add to tsconfig types. |
@cloudflare/vitest-pool-workers | Run Vitest tests inside a real Workers isolate, not just Node. |
create-cloudflare (c3) | npm create cloudflare@latest scaffolder. Wraps wrangler. |
@cloudflare/next-on-pages | Adapter to deploy Next.js apps to Pages. |
@astrojs/cloudflare, @sveltejs/adapter-cloudflare, nitro-cloudflare-pages | Framework adapters. |
hono | The de-facto Workers-first web framework. Pairs with wrangler bindings directly. |
itty-router | Smaller routing library; also Workers-native. |
Testing & CI integration
Two test models: unit tests in plain Node (mocking bindings), and integration tests in a real Workers isolate via @cloudflare/vitest-pool-workers.
Vitest in a Workers isolate
// vitest.config.ts
import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config";
export default defineWorkersConfig({
test: {
poolOptions: {
workers: {
wrangler: { configPath: "./wrangler.jsonc" },
},
},
},
});
// src/worker.test.ts
import { env, createExecutionContext, waitOnExecutionContext } from "cloudflare:test";
import { describe, it, expect } from "vitest";
import worker from "./index";
describe("worker", () => {
it("returns 200 for /", async () => {
const req = new Request("https://example.com/");
const ctx = createExecutionContext();
const res = await worker.fetch(req, env, ctx);
await waitOnExecutionContext(ctx);
expect(res.status).toBe(200);
});
it("writes to KV", async () => {
await env.CACHE.put("k", "v");
expect(await env.CACHE.get("k")).toBe("v");
});
});
Output: 2 passed — tests run in a real workerd isolate with isolated KV / D1 instances.
CI deploy after passing tests
jobs:
test-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- run: npm test
- run: npx wrangler deploy
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
Security considerations
- API tokens > global keys. Generate scoped tokens with minimum permissions. A "Workers Scripts:Edit" token for one account is sufficient for deploys.
- Secrets vs vars.
[vars]in config is plaintext, committed to git, visible in the dashboard.wrangler secret putis encrypted at rest and never logged. Default to secrets and only put non-sensitive config invars. .dev.varsis for local only. It must be gitignored — the file holds dev secrets in plaintext. Add.dev.vars*to.gitignorebefore the first commit.- Token rotation. Rotate API tokens at least quarterly. Revoke immediately after CI provider migrations.
- CORS and origin verification. Workers run at the edge — every CORS, CSRF, and auth check is your responsibility. Don't rely on browser same-origin to gate access.
unsafe-evaland dynamic code. Workers' V8 isolate denieseval/new Function(...)by default (mitigation against script injection). Don't disable it.- Audit transitive deps before deploying. Many npm packages assume Node — they may quietly use
child_processorfsand crash on Workers, or worse, leak data via misconfigured polyfills.wrangler devcatches most of these locally. - Logpush destinations. When configuring Logpush to S3 / R2, the destination credentials live in Cloudflare. Use IAM-scoped credentials that can only write, not read.
- D1 SQL injection. Use parameterised queries (
stmt.bind(...)) — D1's prepare/bind interface is the safe path. Concatenating user input into SQL strings is the same risk as on any Postgres-style backend.
Troubleshooting common errors
Error: A request to the Cloudflare API failed: 10000 — Authentication error — token missing or wrong account. Set CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID, or run wrangler login.
Could not find any wrangler.toml or wrangler.jsonc — run from the project root, or pass --config path/to/wrangler.jsonc.
A Worker with this name is already deployed in another account — names are globally unique on *.workers.dev. Choose a different name or use a custom domain route.
Cannot find module 'node:fs' at runtime — nodejs_compat not enabled in compatibility_flags. Add it and re-deploy.
Script will exceed the size limit — bundle > 1 MB (free) / 10 MB (paid). Audit deps with wrangler deploy --dry-run --outdir dist and inspect dist/. Common culprits: prismjs with all languages, full-icu, huge JSON fixtures.
KV / R2 / D1 Cannot read properties of undefined — binding name in code doesn't match wrangler.jsonc. Confirm env.CACHE matches "binding": "CACHE" exactly.
Could not resolve "fs/promises" at bundle time — a CJS dep imports it conditionally. Add nodejs_compat to flags and re-bundle.
wrangler dev doesn't reload on file change — file watcher hit the OS limit (Linux's inotify). Bump fs.inotify.max_user_watches.
wrangler tail shows nothing — wrong env, or your Worker isn't being hit. Add --env staging and verify the URL.
Local KV / D1 data persists unexpectedly between runs — Miniflare persists to .wrangler/state/ by default. Delete to reset, or pass --persist-to none (newer wrangler versions).
Compatibility date is in the future — set within the supported range. The list of valid dates is in the Cloudflare docs.
When NOT to use this
Skip wrangler when:
- You aren't deploying to Cloudflare. Wrangler is single-vendor. For Vercel, use
vercel; for Netlify,netlify-cli; for AWS, CDK / SST / SAM. - You only need static hosting and use Pages' Git integration. A Git-connected Pages project auto-deploys on push — no wrangler in your local toolchain.
- You build a Workers project entirely from the dashboard. The Cloudflare dashboard offers an in-browser editor for tiny Workers. Fine for prototypes; doesn't scale.
- You use a higher-level platform on top.
next-on-pages,@astrojs/cloudflare, SvelteKit's adapter all wrap wrangler. You may not need to call it directly day-to-day. - You're in a corporate environment that blocks Cloudflare egress. Wrangler talks to the Cloudflare API; if outbound HTTPS to
api.cloudflare.comis blocked, every command fails. - You want everything via Terraform / Pulumi. The Cloudflare provider in those tools manages Workers, KV, R2, D1 declaratively. You'd still use wrangler for
wrangler devandwrangler tail, but deploys live in IaC.
See also
- JavaScript: wrangler — full command reference, bindings, deploy recipes
- JavaScript: astro — Astro on Cloudflare Pages via wrangler
- Concept: http — request/response semantics in the Workers runtime