Quick start · project shape

Type-checked JavaScript for Cloudflare Workers

This is the setup for a Cloudflare Workers project written in JavaScript. The runtime is not a browser and not Node.js, so the checker gets its own types: @cloudflare/workers-types declares Request, Response, ExecutionContext and the rest of the runtime, and lib leaves the DOM out.

  • Cloudflare Workers
  • 2 dev dependencies
  • npm run lint

Your handler is typed with one JSDoc tag, @satisfies {ExportedHandler<Env>}, which types request, env and ctx for you, and Env is a @typedef that lists your bindings. The files below are one project, checked with TypeScript 5.9 when this site is built.

Install

Both packages are development dependencies. Cloudflare publishes @cloudflare/workers-types with the types of the latest runtime; the wrangler types section below covers the generator Cloudflare recommends for a project's own types.

Terminal
npm install --save-dev typescript @cloudflare/workers-types

jsconfig.json

lib: ["es2024"] gives you the ECMAScript library and no DOM, and types loads the runtime's own declarations, which include fetch, Request, Response and console. moduleResolution: "bundler" matches how Wrangler bundles your code before upload, so extensionless relative imports are accepted.

jsconfig.json
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "target": "es2024",
    "lib": ["es2024"],
    "types": ["@cloudflare/workers-types"],
    "skipLibCheck": true
  },
  "include": ["src/**/*.js"]
}

npm scripts

lint checks the project; it does not need Wrangler or an account. Run it before wrangler deploy, or in CI.

package.json
{
  "type": "module",
  "scripts": {
    "lint": "tsc -p jsconfig.json",
    "lint:watch": "tsc -p jsconfig.json --watch --preserveWatchOutput"
  }
}

Typing Env

Bindings (variables, secrets, storage, other services) arrive on the env parameter. Describe them in a @typedef with the same names as in your Wrangler config; a misspelled name is then an error, as below. Keep the two in step by hand, or let wrangler types write the type for you.

wrangler.jsonc
{
  "name": "hello",
  "main": "src/index.js",
  "compatibility_date": "2026-10-01",
  "vars": {
    "GREETING": "Hello"
  }
}
src/index.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: src/index.js(12,32): error TS2551: Property 'GREETNG' does not exist on type 'Env'. Did you mean 'GREETING'? 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. src/index.js(12,32): error TS2551: Property 'GREETNG' does not exist on type 'Env'. Did you mean 'GREETING'? 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. src/index.js(12,32): error TS2551: Property 'GREETNG' does not exist on type 'Env'. Did you mean 'GREETING'? checked 21 October 2026
/**
 * The bindings declared in wrangler.jsonc.
 * @typedef {object} Env
 * @property {string} GREETING
 */

export default /** @satisfies {ExportedHandler<Env>} */ ({
  async fetch(request, env, ctx) {
    const url = new URL(request.url);
    ctx.waitUntil(Promise.resolve(console.log(`${request.method} ${url.pathname}`)));
    return new Response(`${env.GREETNG} from ${url.pathname}`);error TS2551: Property 'GREETNG' does not exist on type 'Env'. Did you mean 'GREETING'?
  },
});
A misspelled binding name.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "target": "es2024",
    "lib": [
      "es2024"
    ],
    "types": [
      "@cloudflare/workers-types"
    ],
    "skipLibCheck": true
  },
  "include": [
    "src/**/*.js"
  ]
}

wrangler types

Cloudflare's documentation recommends npx wrangler types for a project's runtime types: it writes worker-configuration.d.ts, with an Env type from your bindings and runtime types matched to your compatibility date and flags. Point types at that file instead of the package, delete your own Env typedef (the global one replaces it), and run the command again after every config change; wrangler types --check fails CI when the file is out of date.

On 20 October 2026 we checked the handler on this page, without its Env typedef, against the file Wrangler 4.151 wrote for this config: no errors. The examples keep the package, because the written file is large and tied to one Wrangler release. If you turn on Node.js compatibility, the same documentation says to install @types/node and add "node" to types.

jsconfig.json, with wrangler types
{
  "compilerOptions": {
    // …the options above, with types pointing at the written file
    "types": ["./worker-configuration.d.ts"]
  }
}

The example project

A fetch handler that reads a variable binding and schedules work after the response with ctx.waitUntil.

src/index.js TypeScript 5.9.3 TypeScript 5.9.3, run 21 October 2026: No errors. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. No errors. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. No errors. checked 21 October 2026
/**
 * The bindings declared in wrangler.jsonc.
 * @typedef {object} Env
 * @property {string} GREETING
 */

export default /** @satisfies {ExportedHandler<Env>} */ ({
  async fetch(request, env, ctx) {
    const url = new URL(request.url);
    ctx.waitUntil(Promise.resolve(console.log(`${request.method} ${url.pathname}`)));
    return new Response(`${env.GREETING} from ${url.pathname}`);
  },
});
A fetch handler, typed with @satisfies.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "target": "es2024",
    "lib": [
      "es2024"
    ],
    "types": [
      "@cloudflare/workers-types"
    ],
    "skipLibCheck": true
  },
  "include": [
    "src/**/*.js"
  ]
}

In your editor

VS Code reads this jsconfig.json with no further setup. To make it use the same TypeScript as npm run lint, see VS Code.

Sources

Last reviewed 20 October 2026.