TS2307 Cannot find module (tested)
error TS2307: Cannot find module 'X' or its corresponding type declarations means tsc could not resolve the module specifier at all, or found the file but no types for it. We reproduced four common causes in a real TypeScript 7 project and recorded the exact text tsc prints for each, because the fix is different every time.
1. The error, and the two things it actually means
TS2307 fires in two different situations that share one message:
- tsc cannot find the module at all (no package, no file at that path).
- tsc found the module but there are no type declarations for it to use.
In our tests, a genuinely missing package always produced TS2307. But an installed package or local file with no .d.ts usually produced a different code, TS7016, not TS2307 (section 3). Mixing these up wastes time, so we measured both.
2. Missing package entirely
Project: minimal tsconfig.json ("moduleResolution": "bundler", "strict": true), one file importing a package that was never installed:
import x from 'totally-fake-pkg-xyz';
console.log(x);
npx tsc --noEmit printed:
index.ts(1,15): error TS2307: Cannot find module 'totally-fake-pkg-xyz' or its corresponding type declarations.
Fix: install it (npm i totally-fake-pkg-xyz) or fix the typo in the package name. If it's an internal/private package, check it's actually in node_modules and listed in package.json.
3. Installed package with no types: TS7016, not TS2307
We installed the real npm package is-odd (plain JS, no bundled .d.ts, strict mode on) and imported it:
import isOdd from 'is-odd';
console.log(isOdd(3));
tsc did not print TS2307. It printed:
index.ts(1,19): error TS7016: Could not find a declaration file for module 'is-odd'. '/project/node_modules/is-odd/index.js' implicitly has an 'any' type.
Try `npm i --save-dev @types/is-odd` if it exists or add a new declaration (.d.ts) file containing `declare module 'is-odd';`
tsc's own suggestion worked. Installing the types package cleared the error with no code changes:
npm i -D @types/is-odd
After that, tsc --noEmit exited 0. If no @types package exists for your library, add the one-line declaration yourself in any .d.ts file in your project:
declare module 'is-odd';
We also reproduced this with a local untyped file instead of an npm package: a plain legacy.js with allowJs: false, imported from a .ts file, under strict: true. Same result: TS7016, not TS2307. With strict: false (specifically noImplicitAny: false), the identical import compiled with no error at all — tsc silently gave it type any. So whether you see any error here depends on your noImplicitAny setting, which is part of strict.
4. Wrong relative path, and how moduleResolution changes the message
We created utils.ts exporting helper, then imported it with a typo'd path missing the s:
import { helper } from './util';
With "moduleResolution": "bundler" (and also with "nodenext" in a CommonJS package), tsc printed the familiar:
index.ts(1,24): error TS2307: Cannot find module './util' or its corresponding type declarations.
But once we set "module": "nodenext", "moduleResolution": "nodenext" and "type": "module" in package.json (true ESM resolution), the exact same typo produced a completely different error code, not TS2307:
index.ts(1,24): error TS2834: Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Consider adding an extension to the import path.
Under nodenext's ESM rules, tsc flags the missing extension before it even tries to find the file. Fix: correct the typo (./utils), and if you're on node16/nodenext, also add the real extension the runtime will load, e.g. import { helper } from './utils.js' (yes, .js even though the source file is utils.ts — that's what Node's ESM loader resolves at runtime).
5. Importing CSS, SVG, or other assets
Plain tsc (no bundler plugin) doesn't know what a .css file is. We tested two import styles against the same file and got two different errors. A bare side-effect import:
import './style.css';
produced a newer, more specific code in TypeScript 7:
index.ts(1,8): error TS2882: Cannot find module or type declarations for side-effect import of './style.css'.
But importing a default value from the same file:
import styles from './style.css';
produced the classic TS2307:
index.ts(1,20): error TS2307: Cannot find module './style.css' or its corresponding type declarations.
Fix: add an ambient module declaration so tsc treats any .css (or .svg, .png, etc.) import as valid. Put this in any .d.ts file included in your project (e.g. declarations.d.ts):
declare module '*.css';
After adding that one line, both import styles above compiled cleanly in our test. If you use webpack, Vite, or a CSS-modules loader, it typically ships a more specific version of this declaration (e.g. typing the default export as a class-name map) — check its docs before writing your own.
6. Quick checklist
- Package never installed, or name typo'd → TS2307. Install it or fix the name.
- Package or local file exists but has no types → usually TS7016, not TS2307. Install
@types/<pkg>or adddeclare module '<name>';. - Relative path typo under
bundler/classicnoderesolution → TS2307. UndernodenextESM → you may get TS2834 (missing extension) instead, even for the same typo. - Non-JS asset import (CSS/SVG/etc.) → TS2307 or TS2882 depending on import style. Add a
declare module '*.ext'ambient declaration. - Restart your editor's TS server after adding a new
.d.tsfile; VS Code sometimes caches the old resolution.
Frequently asked questions
Is TS2307 always a missing npm package?
No. In our tests, a package that was never installed gave TS2307, but an installed package with no type declarations gave TS7016 instead. Check the code number, not just the 'Cannot find module' wording.
Why did my CSS import fail differently on two lines?
In TypeScript 7 we measured a side-effect import (import './style.css';) giving the newer TS2882, while a default import (import x from './style.css') of the identical file gave TS2307. Both are fixed the same way: declare module '*.css';.
Does changing moduleResolution really change the error code, not just the message?
Yes, in our test. The same typo'd relative path gave TS2307 under moduleResolution 'bundler', and TS2834 (missing file extension) under 'nodenext' with an ESM package.json. nodenext checks extensions before it reports a missing module.
Why does npm install fix TS2307 for a path I typed correctly?
If the specifier is a package name rather than a relative path, TS2307 for a correct-looking name usually means the package truly isn't in node_modules yet, or it's a devDependency that didn't get installed (e.g. npm ci --omit=dev). Run npm ls <pkg> to confirm it's there.
What if there's no @types package and I don't want to write declarations?
Add a one-line declare module '<package-name>'; in any .d.ts file in your project. It silences the error by typing the whole module as any, which we confirmed clears both TS2307 and TS7016, at the cost of losing type checking for that import.
Written with AI assistance. Measured on 2026-10-02 with TypeScript 7.0.2 (npx tsc), Node.js 24.21.0 and npm 11.19.0, in a fresh project (npm init -y && npm i -D typescript). All error text below is copy-pasted from real tsc --noEmit runs, one minimal project per scenario. PlainHeaders is our extension; curl, Python, Node.js and Chromium are free.