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.
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.
{
"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.
{
"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.
{
"name": "hello",
"main": "src/index.js",
"compatibility_date": "2026-10-01",
"vars": {
"GREETING": "Hello"
}
} /**
* 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'?
},
});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.
{
"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.
/**
* 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}`);
},
});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
- Languages: JavaScript · Cloudflare docs
- Languages: TypeScript · Cloudflare docs
- @cloudflare/workers-types · npm
Last reviewed 20 October 2026.