TypeScript

Fix Cannot find name 'console' in TypeScript (TS2584)

console.log fails to type-check in a Node project. See which two tsconfig settings hide console, why the compiler hint points the wrong way, and the fix.

Mahdi Ben RhoumaMahdi Ben Rhouma
Updated
7 min read

TL;DR#

Cannot find name 'console' means no file in your compilation declares console. TypeScript only gets it from three places: the dom lib, the webworker lib, or the @types/node package. The error appears when your tsconfig.json sets lib to ECMAScript-only entries and Node's types are not loaded. For Node code, run npm install --save-dev @types/node and add "types": ["node"] to compilerOptions. On TypeScript 6.0 and later that second step is no longer optional, because types now defaults to an empty list. An ECMAScript-only lib without a recent @types/node also breaks axios's typings, with Cannot find name 'AbortSignal' during npm run build.

The error#

The Stack Overflow question behind this page shows the classic case: a plain console.log(message) inside a function, flagged by the editor as [ts] Cannot find name 'console'. A current compiler adds a code and a hint. Run npx tsc --noEmit on the same kind of project and you get:

text
src/index.ts(2,3): error TS2584: Cannot find name 'console'. Do you need to change your target library? Try changing the 'lib' compiler option to include 'dom'.
src/index.ts(3,15): error TS2591: Cannot find name 'process'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig.

Pay attention to the second line when it appears. TS2591 for process confirms the real problem: Node's type definitions are missing from the program. The console hint suggests dom only because dom is one of the libs that declares it.

Why it happens#

Two separate tsconfig.json settings decide whether console exists.

lib controls the built-in declarations. If you leave lib out, TypeScript loads a default set that, per the lib reference, covers built-in JavaScript APIs and "type definitions for things found in browser environments (like document)". That default set includes console. Once you write "lib": ["es2022"], you replace the default with exactly that list. The ES libs describe the language, and the language has no console. The only built-in declarations of it are in lib.dom.d.ts and lib.webworker.d.ts.

types controls which global @types packages load. Node's console is declared by console.d.ts in @types/node, and it only counts if that package is in the program. The types reference spells out the rules:

  • Before TypeScript 6.0, with no types field, "all visible @types packages are included", meaning every package under node_modules/@types in any parent folder.
  • With an explicit types list, "only packages listed will be included in the global scope". A list such as ["jest"] or ["reflect-metadata"] silently drops @types/node, and with it console and process. This is the case the accepted answer on the question warns about.
  • From TypeScript 6.0, the default is an empty list. The TypeScript 6.0 release notes say it directly: "In TypeScript 6.0, the default types value will be []", and "You will likely need to add "types": ["node"]". The stated reason is build speed, since enumerating every @types package "can be very expensive".

Put those together and you get the full picture of when tsc reports the error:

libtypes@types/node installedTS 5.9TS 6.0+
not setnot setnoconsole fine, process failssame
["es2022"]not setyespassesTS2584 on console
["es2022"]["node"]yespassespasses
["es2022"]list without nodeyesTS2584 on consoleTS2584 on console
["es2022"]["node"]noTS2688TS2688

The second row explains why many projects hit this error for the first time after upgrading the compiler: nothing changed in tsconfig.json, but @types/node stopped being loaded implicitly. The upgrade has other breaking defaults too, covered in the TypeScript 6.0 migration guide for Next.js and Supabase. That guide does not cover the types change, so check both.

Fix#

The fix depends on where the code runs. For a Node.js service, script or CLI, follow steps 1 and 2. Add step 3 if you use a test runner with global functions, and step 4 only for code that genuinely runs in a browser.

1. Install the Node type definitions#

Install @types/node as a dev dependency, pinned to the major version of the Node runtime you deploy on (Node 22 here):

bash
npm install --save-dev @types/node@22

The DefinitelyTyped README explains that an @types package's major and minor numbers track those of the library it describes. Matching the major stops the compiler from accepting APIs your runtime does not have. If the compiler and the type packages have drifted apart after a blanket upgrade, aligning TypeScript and @types/node covers realigning both in one pass. That page installs @types/node@latest; for a deployed service, keep @types/node on your runtime's major instead.

2. List node in compilerOptions.types#

Add node to types explicitly. This is the only configuration that behaves the same on TypeScript 5.9 and 6.0+. A complete tsconfig.json for a Node project:

json
{
  "compilerOptions": {
    "target": "es2022",
    "module": "nodenext",
    "moduleResolution": "nodenext",
    "lib": ["es2022"],
    "types": ["node"],
    "strict": true,
    "outDir": "dist",
    "rootDir": "src"
  },
  "include": ["src"]
}

If your config already had a types list, add "node" to it rather than deleting the list. Deleting it on 6.0 leaves you with the empty default and the same error.

3. Add your test runner's types next to node#

Test files use globals such as describe and it, which are a second global package. Install the runner's types and list both. The release notes use this exact pair as their example:

bash
npm install --save-dev @types/jest
json
{
  "compilerOptions": {
    "target": "es2022",
    "module": "nodenext",
    "moduleResolution": "nodenext",
    "lib": ["es2022"],
    "types": ["node", "jest"],
    "strict": true
  },
  "include": ["src", "test"]
}

4. Browser code only: add dom to lib#

The second-most-voted answer on the question is to add "dom" to lib. That fix is correct for code that runs in a browser (a React or Next.js front end, for example), where window, document and console all exist at runtime:

json
{
  "compilerOptions": {
    "target": "es2022",
    "module": "esnext",
    "moduleResolution": "bundler",
    "lib": ["es2022", "dom"],
    "jsx": "react-jsx",
    "strict": true
  },
  "include": ["src"]
}

For Node-only code it is the wrong fix, despite what the TS2584 hint says. dom declares window, document and localStorage as globals. A server file that touches document.title would then type-check and crash at runtime with a ReferenceError, which is exactly the kind of mistake the compiler is there to stop. Browser globals that you add yourself belong in a global declaration instead, as shown in how to add a property to window in TypeScript.

Verify the fix#

Type-check without emitting output:

bash
npx tsc --noEmit

It should exit with no errors. To confirm that @types/node is loaded, and why, use --explainFiles, which prints every file in the compilation with the reason it was included:

bash
npx tsc --noEmit --explainFiles | grep -A1 "^node_modules/@types/node/index.d.ts$"

With "types": ["node"] in place, the line under the file reads Entry point of type library 'node' specified in compilerOptions. If grep prints nothing, the package is not in the program and console will fail again.

When several configs extend each other, print the final merged configuration to see which lib and types actually apply:

bash
npx tsc --showConfig

A types or lib value inherited through extends from a shared base config is a common reason a fix appears to do nothing.

One email a month — the latest explained errors

What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.

Edge cases the answers skip#

"types": ["*"] is TypeScript 6.0+ only. The release notes offer * to restore the old "load everything" behaviour. On 5.9 the compiler does not recognise it and fails with error TS2688: Cannot find type definition file for '*'. Even on 6.0 it brings back the build cost the change was made to remove (the release notes report 20-50% faster builds from setting types appropriately), so list packages by name instead. If TS2688 names a package you never put in types, such as body-parser, the cause is usually an @types folder without a declaration file, which TypeScript before 6.0 loads automatically; see TS2688 for a package you never listed.

"node" listed but not installed. If types names a package that is not installed, the error changes to error TS2688: Cannot find type definition file for 'node'. In a pnpm or Yarn workspace this usually means @types/node is installed in another package but not resolvable from the package being compiled. Install it in the package that owns the tsconfig.json.

The triple-slash workaround. Placing /// <reference types="node" /> at the top of a file also brings @types/node in. The triple-slash directives handbook is explicit that .ts files should use types in tsconfig.json instead and reserves the directive for hand-written declaration files. Because the directive pulls the package into the whole compilation, one stray reference also hides the real configuration problem from every other file.

types does not affect imports. The types option only governs global declarations. An imported package is still typed through its own declarations or its @types package, whatever the types list says. If the error is about a module rather than a global name, you have a different problem: see TS7016, could not find a declaration file for module.

Mixed browser and Node code in one repository. A single config cannot be honest about both runtimes, so split them. The editor's language service only discovers files named tsconfig.json (or jsconfig.json), so a standalone tsconfig.node.json is ignored in the editor unless something points to it. The simplest layout gives each runtime its own folder with its own tsconfig.json: one with dom for the front end, one in the server folder with an ES-only lib and "types": ["node"]. If both configs must live at the root, make the root tsconfig.json a solution file that references them:

json
{
  "files": [],
  "references": [
    { "path": "./tsconfig.app.json" },
    { "path": "./tsconfig.node.json" }
  ]
}

Each referenced config needs "composite": true and its own include. On the command line, build them with npx tsc -b, or check one with npx tsc -p tsconfig.node.json --noEmit. Plain npx tsc on a root file with "files": [] compiles nothing. With either layout the editor reports console, process and document correctly for each part of the codebase.

The error message points at lib, but in a Node project the missing piece is almost always the types list. Load Node's types explicitly, keep dom for code that runs in a browser, and the same configuration will keep compiling across TypeScript upgrades.

Further reading: Fix An index signature parameter type cannot be a union. Also see Fix Vitest: Cannot find namespace vi in TypeScript. Also see Fix TS2688: node_modules Is the Wrong typeRoots. Also see Fix tsc SyntaxError: Unexpected token '?' on old Node.js, TypeScript Getter Setter Errors: TS1056, TS2378, TS2380 Fix.

Frequently Asked Questions

|

Have more questions? Contact us

Still stuck? Paste your error.

Paste the error message or stack trace — we check it against every fix and tool on the site.

Matching happens in your browser. When you search, a cleaned error signature is sent so we can spot errors we do not cover yet — never the raw text, your IP address or your e-mail. Do not paste passwords or keys. Details

Written by

Mahdi Ben Rhouma
Mahdi Ben Rhouma

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote