💻AI Codingadvanced

Wrangler CLI 2026 - Cloudflare Workers Deep Dive for Indian Devs

Ship edge functions from India with Wrangler CLI 2026. Setup, deploy, R2, D1, and cost breakdowns in INR.

By··7 min read
Wrangler CLI 2026 - Cloudflare Workers Deep Dive for Indian Devs

Why Wrangler matters in 2026

Wrangler is the command line tool you use to create, run, configure, and deploy Cloudflare Workers. If you are building APIs for Indian users, the main reason to care is simple: you can move request handling closer to the user without managing servers in Mumbai, Singapore, Frankfurt, and Virginia yourself.

Do not treat edge as magic latency dust. Measure your own routes from Airtel, Jio, office broadband, and mobile networks before promising numbers. A Worker served from a nearby Cloudflare location can be fast from India, but exact latency depends on peering, route, cache state, and whether your Worker calls D1, R2, KV, an origin API, or another service behind the scenes.

As of this polish pass on 2026-05-20, npm reports [email protected]. Cloudflare recommends installing Wrangler locally in each project, not as a global binary. That matters because one repo can pin Wrangler 4.93.0 while another stays on a known-good older 4.x release until you are ready to test an upgrade.

Prerequisites

Use a current Node.js release supported by Cloudflare's tooling. Node.js 22 LTS is a sensible baseline in 2026, and Node.js 24 Current is fine if your team has already standardized on it.

Create a project and install Wrangler as a dev dependency:

npm create cloudflare@latest -- my-api --type=hello-world --ts
cd my-api
npm install --save-dev [email protected]
npx wrangler --version

Authenticate once on your machine:

npx wrangler login

This opens a browser OAuth flow. For CI, do not use browser login. Create an API token with the minimum permissions required for Workers, D1, and R2, then inject it as an environment secret in your CI provider. Before you script anything against that token, read the Cloudflare API token gotchas: the self-update endpoint is a full replace that silently drops scopes, and the wrangler auth error it throws explains nothing.

Project shape

A small TypeScript Worker usually starts with this structure:

my-api/
  src/
    index.ts
  wrangler.toml
  package.json
  tsconfig.json

The wrangler.toml file is the contract between your code and Cloudflare's runtime:

name = "my-api"
main = "src/index.ts"
compatibility_date = "2026-05-20"

The compatibility_date is not decoration. It pins runtime behavior so Cloudflare can ship platform changes without silently changing how your Worker behaves. Review it during planned upgrades, bump it intentionally, and run your test suite before deploying.

Local development

Run the local dev server:

npx wrangler dev

Wrangler starts a local Workers runtime and exposes your Worker on http://localhost:8787 by default. Keep one detail in mind: local development is close to production behavior, but it is not proof that every remote integration is correct. Use local mode while building, then test remote bindings before a serious deploy.

Replace src/index.ts with a minimal API:

export default {
  async fetch(request: Request): Promise<Response> {
    const url = new URL(request.url);

    if (url.pathname === "/health") {
      return Response.json({ status: "ok", region: "local" });
    }

    return new Response("Hello from Cloudflare Workers", { status: 200 });
  },
};

Test it:

curl http://localhost:8787/health

Expected response:

{"status":"ok","region":"local"}

Add D1 for SQLite-backed data

D1 is Cloudflare's serverless SQL database built around SQLite. It is a good fit for compact relational data, internal dashboards, API metadata, and read-heavy product tables. It is not a drop-in replacement for a tuned Postgres cluster with long transactions, large analytical queries, or high write contention.

Create a database:

npx wrangler d1 create my-api-db

Wrangler prints the binding block. Add it to wrangler.toml:

[[d1_databases]]
binding = "DB"
database_name = "my-api-db"
database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

Create schema.sql:

CREATE TABLE IF NOT EXISTS users (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  email TEXT NOT NULL UNIQUE,
  created_at TEXT NOT NULL DEFAULT (datetime('now'))
);

Apply the schema locally:

npx wrangler d1 execute my-api-db --local --file=./schema.sql

Use the binding in TypeScript:

interface Env {
  DB: D1Database;
}

type CreateUserBody = {
  email: string;
};

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

    if (url.pathname === "/users" && request.method === "POST") {
      const body = (await request.json()) as CreateUserBody;

      if (!body.email || !body.email.includes("@")) {
        return Response.json({ error: "invalid email" }, { status: 400 });
      }

      await env.DB.prepare("INSERT INTO users (email) VALUES (?)")
        .bind(body.email)
        .run();

      return Response.json({ created: true }, { status: 201 });
    }

    if (url.pathname === "/users" && request.method === "GET") {
      const { results } = await env.DB.prepare(
        "SELECT id, email, created_at FROM users ORDER BY id DESC LIMIT 50",
      ).all();

      return Response.json(results);
    }

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

Test the flow:

npx wrangler dev
curl -X POST http://localhost:8787/users \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]"}'
curl http://localhost:8787/users

For production, apply migrations to the remote database before deploying code that depends on them:

npx wrangler d1 execute my-api-db --remote --file=./schema.sql

Add R2 for object storage

R2 is Cloudflare's object storage service. The pricing reason to care is egress: Cloudflare's Workers pricing page lists no R2 egress bandwidth charge. You still pay for storage and request classes after the included allowances, so small hot objects can cost more through operations than through bytes stored.

Create a bucket:

npx wrangler r2 bucket create my-api-assets

Add the binding:

[[r2_buckets]]
binding = "ASSETS"
bucket_name = "my-api-assets"

Use the bucket from the Worker:

interface Env {
  ASSETS: R2Bucket;
}

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

    if (url.pathname === "/upload" && request.method === "PUT") {
      const key = url.searchParams.get("key");

      if (!key) {
        return Response.json({ error: "missing key" }, { status: 400 });
      }

      await env.ASSETS.put(key, request.body, {
        httpMetadata: {
          contentType: request.headers.get("content-type") ?? "application/octet-stream",
        },
      });

      return Response.json({ uploaded: key });
    }

    if (url.pathname.startsWith("/files/") && request.method === "GET") {
      const key = decodeURIComponent(url.pathname.replace("/files/", ""));
      const object = await env.ASSETS.get(key);

      if (!object) {
        return new Response("Not found", { status: 404 });
      }

      return new Response(object.body, {
        headers: {
          "Content-Type": object.httpMetadata?.contentType ?? "application/octet-stream",
        },
      });
    }

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

Test it locally:

printf "Hello R2\n" > test.txt
curl -X PUT "http://localhost:8787/upload?key=test.txt" \
  -H "Content-Type: text/plain" \
  --data-binary @test.txt
curl http://localhost:8787/files/test.txt

Deploying

Deploy the Worker:

npx wrangler deploy

Wrangler will print the deployed route, usually a workers.dev URL unless you have configured a custom domain. For a production Indian SaaS, configure a real domain early. It makes cookies, auth redirects, API clients, and monitoring cleaner than shipping public beta traffic on a temporary subdomain.

Use environments for staging and production:

[env.staging]
name = "my-api-staging"

[[env.staging.d1_databases]]
binding = "DB"
database_name = "my-api-db-staging"
database_id = "staging-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

[[env.staging.r2_buckets]]
binding = "ASSETS"
bucket_name = "my-api-assets-staging"

[env.production]
name = "my-api-production"

[[env.production.d1_databases]]
binding = "DB"
database_name = "my-api-db"
database_id = "prod-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

[[env.production.r2_buckets]]
binding = "ASSETS"
bucket_name = "my-api-assets"

Deploy each environment explicitly:

npx wrangler deploy --env staging
npx wrangler deploy --env production

If these deploys run from GitHub Actions and you watch your free minutes drain across repos, move the job to a self-hosted GitHub Actions runner on Oracle ARM and the CI cost drops to zero.

Cost notes for Indian devs

Use USD as the billing source of truth and convert to INR only for budgeting. Exchange rates, card markup, GST treatment, and invoice handling can change. At 1 USD = 83.5 INR, the Workers Paid plan minimum of 5 USD is about 418 INR per month.

The important current limits are these:

Item Free plan Paid plan
Worker requests 100,000 per day 10 million per month included, then usage billing
Worker CPU 10 ms per invocation 30 million CPU ms per month included
D1 rows read 5 million per day 25 billion per month included
D1 rows written 100,000 per day 50 million per month included
D1 storage 5 GB total 5 GB included, then per-GB billing
R2 storage 10 GB-month included per-GB storage after included usage
R2 egress no egress bandwidth charge no egress bandwidth charge

Do not compare Workers to AWS Lambda only on request count. A real comparison includes database reads, object operations, logs, CPU time, static asset behavior, and whether your origin is still in another cloud. For small CRUD APIs, Workers plus D1 plus R2 can stay cheap. For CPU-heavy image processing, large dependency bundles, or long background jobs, the fit is weaker.

Production checklist

Before sending real traffic:

  • Pin wrangler in devDependencies, and update it through pull requests.
  • Keep compatibility_date current, but bump it intentionally.
  • Use npx wrangler secret put JWT_SECRET for secrets.
  • Run npx wrangler tail during launch to catch runtime errors.
  • Check compressed Worker size with npx wrangler deploy --dry-run --outdir bundled.
  • Remember that compressed Worker size limits differ by plan: 3 MB on Free and 10 MB on Paid.
  • Move large static files, model files, and generated assets to R2 or Workers Static Assets.
  • Add indexes to D1 tables before traffic teaches you what full scans cost.

Quick takeaways

  • Use local [email protected], not an unpinned global install.
  • Start with npm create cloudflare@latest, then use npx wrangler dev and npx wrangler deploy.
  • D1 is strong for compact SQLite-backed app data, but you still need indexes and migration discipline.
  • R2 removes egress bandwidth charges, while storage and operation charges still matter.
  • Treat INR costs and India latency numbers as measurements to verify, not constants to copy.

Related