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.

bash
npm install -D wrangler

Output: added wrangler to devDependencies

bash
npx wrangler --version

Output: prints installed wrangler version

bash
pnpm add -D wrangler

Output: added 1 package, linked from store

bash
yarn add -D wrangler

Output: added wrangler to devDependencies

bash
bun add -d wrangler

Output: installed wrangler as a dev dependency

For global use:

bash
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.x line. 3.x supported 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_dev programmatic 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-sdk mono-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 wrangler was 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 source
  • chokidar, chokidar-cli — file-watching
  • selfsigned, 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 isolate
  • c3 (create-cloudflare) — npm create cloudflare@latest scaffolder; 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:

ToolPlatform
vercelVercel — Functions, Edge Functions, Cron, KV, Blob, Postgres
netlify-cliNetlify — Functions, Edge Functions, Forms
aws-cdk / sstAWS Lambda + edge via CloudFront
flyctlFly.io — full machines + edge regions
deno deployDeno Deploy — Deno-native edge runtime
firebaseFirebase Functions, Hosting

For multi-cloud workflows, the OpenNext and Hono ecosystems publish adapters that target wrangler + competitors from the same source.

Common gotchas

  1. 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 run wrangler kv namespace list to verify.
  2. D1 migration drift. wrangler d1 migrations apply runs migrations in ./migrations/. Locally and remotely they tick independently — running locally without applying remotely (or vice versa) silently desyncs. Use wrangler d1 migrations list <db> against each env before deploying.
  3. wrangler dev vs wrangler dev --remote. The default is the local Miniflare emulator. --remote proxies to the real edge so KV/R2/D1 hit production data — convenient but irreversible. Always confirm which env you're targeting before destructive ops.
  4. 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.
  5. Secrets are NOT in wrangler.toml. Use wrangler secret put NAME to store secrets, OR commit a (gitignored) .dev.vars file for local-only secrets. Putting secrets in [vars] writes them to your tracked TOML and leaks them on GitHub.
  6. compatibility_date is 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.
  7. unenv / Node compat is partial. nodejs_compat flag enables a subset of node:* 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".
  8. Account/zone scoping confusion. wrangler whoami shows your token's account. Multi-account setups need CLOUDFLARE_ACCOUNT_ID in env or the --account-id flag, 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:

jsonc
{
  "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:

bash
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:

bash
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:

bash
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:

yaml
- 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.

text
# .dev.vars (gitignored)
DATABASE_URL="postgres://localhost/dev"
STRIPE_SECRET="sk_test_..."
bash
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:

bash
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:

bash
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:

bash
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.dev subdomain
  • [env.preview] = ephemeral preview, sometimes per-branch

Build the deploy command into npm scripts:

json
{
  "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.

yaml
- 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:

bash
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:

bash
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.

bash
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.

FromToLikely changes (verify against the release notes)
wrangler@2wrangler@3TypeScript rewrite (the original was Rust). Config format unchanged. Some legacy CLI flags removed.
wrangler@3wrangler@4Node 20+ floor. wrangler.tomlwrangler.jsonc recommended (TOML still supported). Tightened deploy permissions in tokens. New observability defaults.
Any majornextCompatibility flags policy, unenv Node compat improvements, new binding types, and dashboard-managed deploy options shift frequently.

Recommended upgrade flow:

  1. Read the release notes for every major between current and target. Cloudflare publishes them on the workers-sdk repo.
  2. Bump in a branch: npm install -D wrangler@<target>.
  3. Run wrangler deploy --dry-run --outdir dist to surface config-parsing errors without deploying.
  4. Run wrangler dev and exercise every binding locally.
  5. Deploy to staging first, run smoke tests, then promote.
  6. Refresh compatibility_date at the same time — many breaking runtime behaviours are gated on it, not the wrangler version.

wrangler.tomlwrangler.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.

ConcernPattern
Worker module formatModules-format Workers are ESM (export default { fetch(...) }). Service Workers (addEventListener("fetch", ...)) are legacy and discouraged.
Source languageTypeScript and JavaScript both work. wrangler bundles via esbuild.
Node-compatSet compatibility_flags = ["nodejs_compat"] for node:* imports. Not all modules supported — fs, child_process, native addons, etc., are out.
CommonJS depsesbuild 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 modulesImport with import wasm from "./module.wasm"; then WebAssembly.instantiate(wasm). wrangler bundles the binary into the Worker upload.
unstable_dev programmatic APIimport { 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 limitFree tier: 1 MB compressed. Paid: 10 MB. wrangler reports the size on deploy; over-limit fails the upload.
Tree-shakingesbuild 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 / featurePurpose
Workers KVEventually-consistent global key-value store. Low write throughput, fast global reads.
R2S3-compatible object storage. Zero egress fees.
D1SQLite-on-the-edge. Read replicas globally, primary in one region.
Durable ObjectsSingle-instance globally-coordinated stateful objects with SQLite storage and WebSocket support.
HyperdriveConnection pool for external Postgres / MySQL — caches + accelerates origin DB.
QueuesProducer/consumer messaging with batching, retries, dead-letter queues.
WorkflowsDurable execution engine — long-running step-based workflows with sleep + retry.
Workers AILLM + image + speech models via env.AI.run(model, input).
VectorizeVector database for RAG / semantic search.
Browser RenderingHeadless Chrome via @cloudflare/puppeteer.
Email SendingOutbound transactional email via Email Workers.
Email RoutingInbound email → Worker (handler receives a message).
ContainersRun Docker containers alongside Workers (newer offering — verify availability for your account tier).
Service BindingsWorker-to-Worker calls without external HTTP.
LogpushPush request / error logs to S3, GCS, R2, Datadog.
Companion packagePurpose
miniflareWorkers runtime emulator. Used internally by wrangler dev; also standalone for tests.
@cloudflare/workers-typesTypeScript types for runtime bindings. Add to tsconfig types.
@cloudflare/vitest-pool-workersRun Vitest tests inside a real Workers isolate, not just Node.
create-cloudflare (c3)npm create cloudflare@latest scaffolder. Wraps wrangler.
@cloudflare/next-on-pagesAdapter to deploy Next.js apps to Pages.
@astrojs/cloudflare, @sveltejs/adapter-cloudflare, nitro-cloudflare-pagesFramework adapters.
honoThe de-facto Workers-first web framework. Pairs with wrangler bindings directly.
itty-routerSmaller 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

ts
// vitest.config.ts
import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config";

export default defineWorkersConfig({
  test: {
    poolOptions: {
      workers: {
        wrangler: { configPath: "./wrangler.jsonc" },
      },
    },
  },
});
ts
// 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

yaml
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 put is encrypted at rest and never logged. Default to secrets and only put non-sensitive config in vars.
  • .dev.vars is for local only. It must be gitignored — the file holds dev secrets in plaintext. Add .dev.vars* to .gitignore before 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-eval and dynamic code. Workers' V8 isolate denies eval / 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_process or fs and crash on Workers, or worse, leak data via misconfigured polyfills. wrangler dev catches 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 runtimenodejs_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.com is 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 dev and wrangler tail, but deploys live in IaC.

See also