mrkeyoor.com_
Thu 06 Aug 08:54 UTC
npmInfraupdated 06 Aug 2026

wrangler

Wrangler is the command line tool for Cloudflare Workers. It scaffolds a project, runs your Worker locally on workerd (the same runtime Cloudflare runs in production, driven through Miniflare), and pushes it live with wrangler deploy. Everything the Worker can touch is declared in one config file, wrangler.jsonc or wrangler.toml: the entry script, a compatibility date, and bindings for KV, R2, D1, Durable Objects, Queues, Hyperdrive, Vectorize, and the rest. Wrangler also carries the day-to-day operations commands for those products, so creating a D1 database, running a migration, putting a secret, streaming production logs, and rolling a new version out to a slice of traffic are all subcommands of the same binary. It lives in the cloudflare/workers-sdk monorepo alongside miniflare and the Vite plugin, and it pins the workerd build it ships with, so upgrading wrangler upgrades your local runtime too.

Verdict

If you ship to Cloudflare, wrangler is not a choice you make, it is the platform, and the real workerd runtime plus generated binding types make it a good one. Pin it as a devDependency and actually read the changelog, because near-daily releases keep moving the CLI and the runtime underneath it at the same time.

API stability3/5The v4 command surface has held since March 2025 and deprecations get warnings before removal, but the release rate is extreme (32 releases in the 90 days to 2026-08-06) and workerd plus miniflare are exact-pinned, so a wrangler patch changes your local runtime; 4.119.0 depends on miniflare 5.20260801.0-alpha
Docs5/5The Cloudflare Workers docs cover every command with flags and worked examples, changelog entries explain why a flag exists rather than just naming it, and wrangler docs opens the right page from the terminal; the repo README itself is only a monorepo index
Maintenance5/5Funded Cloudflare team shipping most weekdays, 317 open issues out of 483 open issues and PRs across a monorepo holding nine published packages, and automated bumps keep the bundled workerd matched to the deployed runtime
Ecosystem5/5About 18M downloads a week and mandatory for every Cloudflare Workers project; the wrangler config format is read by @cloudflare/vite-plugin, @cloudflare/vitest-pool-workers, and the framework adapters, so it is the shared contract for the whole platform

Use it if

  • You deploy to Cloudflare Workers or Pages at all: this is the only first-party deploy path, and the one Cloudflare tests its runtime releases against
  • You want local development that matches production: wrangler dev runs real workerd rather than a Node shim, so an API the edge does not support fails on your laptop instead of after deploy
  • You want typed bindings: wrangler types reads wrangler.jsonc and writes worker-configuration.d.ts with an Env interface plus the runtime globals, so TypeScript knows what env.CACHE actually is
  • You need progressive rollouts or a fast undo: wrangler versions upload then wrangler versions deploy splits traffic between two versions, and wrangler rollback puts the previous one back
  • You run CI: set CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID and every command runs non-interactively with no browser login
Skip it if

Setup reality

npm create cloudflare@latest scaffolds a project and pins wrangler as a devDependency, which is what you want, because a globally installed wrangler will drift from what CI runs. Two install requirements bite early: Node 22 or newer, and a platform-specific workerd binary pulled at install time, so Docker images need a matching libc and CI caches have to be per-platform. The first command opens a browser for OAuth; on a headless box use wrangler login --device or skip login entirely by exporting CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID. Then the config catches you twice. compatibility_date is mandatory and freezes runtime behavior at that date, so a project left on a two-year-old date quietly keeps old semantics forever. And Node builtins throw at import until you add nodejs_compat to compatibility_flags. Finally, every binding in your config must point at a resource that already exists, which means a first-day round of wrangler kv namespace create and wrangler d1 create with ids copied into wrangler.jsonc by hand.

Patterns

Start a Worker and run it locallyscaffold-and-dev

npm create cloudflare@latest my-worker -- --type=hello-world
cd my-worker

npx wrangler dev              # local workerd on http://localhost:8787
npx wrangler dev --remote     # same code on the real edge, real bindings
npx wrangler dev --port 3000 --persist-to .cache/wrangler

dev is local by default: workerd plus Miniflare's simulated KV, R2, and D1 under .wrangler/state. --remote runs on Cloudflare's network against production resources, which is the only way to see real geo headers, real cache behavior, or data that already exists in a namespace.

The wrangler.jsonc that everything readsconfig-file

// wrangler.jsonc
{
  "name": "my-worker",
  "main": "src/index.ts",
  "compatibility_date": "2026-08-01",
  "compatibility_flags": ["nodejs_compat"],
  "observability": { "enabled": true },
  "vars": { "LOG_LEVEL": "info" },
  "kv_namespaces": [
    { "binding": "CACHE", "id": "b1f0e2..." }
  ],
  "d1_databases": [
    { "binding": "DB", "database_name": "app", "database_id": "9c2e41..." }
  ]
}

compatibility_date is required and pins runtime behavior to that date, so an old date keeps old bug-for-bug semantics silently. Importing node:buffer or node:crypto throws until nodejs_compat is in compatibility_flags. The ids are real account resources, so this file cannot be committed with placeholders and still deploy.

Keep Env in sync with your bindingsgenerate-types

npx wrangler types                              # writes worker-configuration.d.ts
npx wrangler types --env-interface CloudflareEnv
npx wrangler types --include-runtime=false      # Env only, no runtime globals

Runtime types are included by default now, which is why @cloudflare/workers-types is only an optional peer dependency. Rerun this every time you touch bindings or Env goes stale and TypeScript will happily let you read env.CACHE when there is no such binding at runtime. Put it in a predev and prebuild script instead of trusting memory.

Secrets in production, .dev.vars locallysecrets-and-local-vars

echo "sk_live_abc" | npx wrangler secret put STRIPE_KEY
npx wrangler secret list
npx wrangler secret bulk secrets.json

# .dev.vars (gitignored, only read by wrangler dev)
# STRIPE_KEY="sk_test_abc"
# DATABASE_URL="postgres://localhost/dev"

Secrets set with wrangler secret live on Cloudflare and never touch your repo, but wrangler dev cannot see them; it reads .dev.vars instead, so a missing entry there shows up as undefined only in local runs. Anything under vars in wrangler.jsonc is plaintext in git and in the dashboard, so keep that for log levels and feature flags.

Create the KV, R2, and D1 resources your bindings point atcreate-resources

npx wrangler kv namespace create CACHE       # prints the config snippet to paste
npx wrangler kv key put --binding=CACHE hello world --remote

npx wrangler r2 bucket create uploads

npx wrangler d1 create app
npx wrangler d1 execute app --remote --command "select name from sqlite_master"
npx wrangler d1 migrations apply app --remote

Every create command prints a config block you paste into wrangler.jsonc yourself; nothing writes it back, and a wrong id fails at deploy rather than at edit time. Pass --local or --remote explicitly on kv key and d1 execute: the default sends you to .wrangler/state, and debugging rows that only exist on your laptop costs an afternoon.

Deploy, and see what would be deployed firstdeploy-and-dry-run

npx wrangler deploy
npx wrangler deploy --dry-run --outdir dist    # bundle only, no upload, no auth
npx wrangler deploy --minify
npx wrangler whoami

--dry-run --outdir is the fastest way to answer "why is my Worker over the size limit", because it writes the exact bundle esbuild produced without needing credentials. It is also the check to run on pull requests, since it catches a broken import or a missing binding without touching production.

Staging and production from one configenvironments

// wrangler.jsonc
{
  "name": "my-worker",
  "main": "src/index.ts",
  "compatibility_date": "2026-08-01",
  "vars": { "LOG_LEVEL": "info" },
  "env": {
    "staging": {
      "name": "my-worker-staging",
      "vars": { "LOG_LEVEL": "debug" },
      "kv_namespaces": [{ "binding": "CACHE", "id": "a44c..." }]
    }
  }
}

Named environments do not inherit bindings from the top level. If staging declares any kv_namespaces it must declare all of them, and forgetting one gives you a Worker that deploys fine and then throws on the first request. vars behave the same way. Deploy with wrangler deploy --env staging and dev with wrangler dev --env staging.

Stream logs from a deployed Workertail-logs

npx wrangler tail
npx wrangler tail --format pretty --status error
npx wrangler tail --search "checkout" --method POST
npx wrangler tail --ip self --format json | jq '.logs[].message'

tail is a live stream with no history, so it only shows what happens while you are attached; for anything after the fact you need observability enabled in the config and the dashboard or Logpush. Sampling kicks in on high-traffic Workers, so an absent log line does not prove the code path did not run.

Ship a version to a slice of trafficgradual-rollout

npx wrangler versions upload                 # uploads without serving traffic
npx wrangler versions list
npx wrangler versions deploy <new-id>@10% <old-id>@90%

# happy with it, take it to 100%
npx wrangler versions deploy <new-id>@100%

wrangler deploy always goes straight to 100%, so gradual rollout is a different command pair entirely: upload then deploy. Percentages must add up to 100 and only two versions can be live at once. Durable Object migrations and some binding changes cannot be split this way and will be rejected at upload.

Undo a bad deployrollback

npx wrangler deployments list
npx wrangler deployments status
npx wrangler rollback --message "checkout 500s"
npx wrangler rollback <version-id>

Rollback swaps the served version back, which is fast, but it does not touch anything outside the script: secrets you changed, D1 migrations you applied, and KV writes stay exactly as the bad deploy left them. Treat it as a way to stop the bleeding, not as an undo.

Scheduled Workers and how to test themcron-triggers

// wrangler.jsonc
{ "triggers": { "crons": ["0 * * * *", "*/15 * * * *"] } }

// src/index.ts
export default {
  async scheduled(event: ScheduledController, env: Env, ctx: ExecutionContext) {
    ctx.waitUntil(env.DB.prepare("delete from sessions where expires_at < ?")
      .bind(Date.now()).run())
  }
} satisfies ExportedHandler<Env>

Crons run in UTC, always, with no timezone option. Locally they do not fire on a schedule at all: start wrangler dev --test-scheduled and hit http://localhost:8787/__scheduled to invoke the handler by hand. Work started without ctx.waitUntil can be cut off when the handler returns.

Deploy from CI without a browser loginci-deploy

# .github/workflows/deploy.yml
- uses: actions/setup-node@v4
  with: { node-version: 22 }
- run: npm ci
- run: npx wrangler deploy --dry-run --outdir dist
- run: npx wrangler deploy
  env:
    CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
    CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}

The API token needs the Workers Scripts edit permission plus one permission per product you bind to, and a token that works for deploy will still fail on wrangler d1 execute until you add D1 edit. Node 22 is not optional here. Add WRANGLER_SEND_METRICS=false if you do not want telemetry from build agents.

Alternatives

PackageRegistryPick it when
@cloudflare/vite-pluginnpmYou are building a Vite app for Workers and want one dev server for the frontend and the Worker instead of running wrangler dev beside it
miniflarenpmYou only want the local Workers runtime embedded in your own scripts or tests, without the deploy, auth, and account management commands
alchemynpmYou want Cloudflare resources declared as code with state and diffs, rather than created imperatively with ids pasted into wrangler.jsonc
vercelnpmYour app is Next.js shaped and you would rather keep a mature framework deploy story than port everything onto Workers bindings