TS2307Semantic Error
Since TS 1.0Updated in TS 1.5Updated in TS 3.9

Fix TS2307: Cannot Find Module

Learn why TypeScript throws TS2307 when it can't resolve an import, and how to fix missing packages, tsconfig paths aliases, and asset imports.

error TS2307: Cannot find module 'X' or its corresponding type declarations

What This Error Means

TypeScript error TS2307 means the compiler could not resolve an import. It took the string in your import (or require, or dynamic import()), walked it through the module resolution algorithm configured in your tsconfig.json, and reached the end of the search without finding a .ts, .tsx or .d.ts file — or a package.json whose types / exports entry points at one.

The important part is that this is a resolution failure, not a type-checking failure. TypeScript is not complaining about the shape of what you imported; it never got far enough to look. Which paths it tries depends on moduleResolution, baseUrl + paths, typeRoots, allowJs and friends — and crucially, it never depends on your bundler's aliases. Vite, webpack, Next.js and Jest each resolve modules their own way, so a build that runs perfectly can still fail tsc.

// The general shape of the error:
// Cannot find module 'dayjs' or its corresponding type declarations.
//                     ~~~~~                       ~~~~~~~~~~~~~~~~~
//                     the specifier               no .d.ts found either

That "or its corresponding type declarations" tail arrived in TypeScript 3.9. It exists because finding a plain .js file is not enough on its own — the compiler needs types. If the JavaScript is resolvable and noImplicitAny is on, you get TS7016 instead, which is the friendlier sibling of this error. Older toolchains say just Cannot find module 'X'. (TypeScript 1.5 to 3.8) or Cannot find external module 'X' (1.4 and earlier), so old answers you find online are describing the same check.

Common Causes

1. The Package Is Not Installed

The most common cause is the most boring one: the dependency simply is not in node_modules. In a monorepo it may be installed for a sibling workspace but not for the one you are editing.

// ❌ Broken
import dayjs from "dayjs"
//                ~~~~~~~ Error: Cannot find module 'dayjs' or its corresponding type declarations.
 
export const placedAt = dayjs("2026-08-29").format("DD.MM.YYYY")
# ✅ Fixed — install it where the importing package lives
npm install dayjs          # dayjs ships its own .d.ts files
 
# for a library that ships no types of its own, add the community ones:
npm install --save-dev @types/library-name

In a workspace repo, confirm which copy the compiler will see with npm ls dayjs before you start editing config. And if the install clearly succeeded but VS Code still underlines the import, run TypeScript: Restart TS Server from the command palette — the editor's language service caches resolution results.

2. A Path Alias Your Bundler Knows and TypeScript Doesn't

Aliases like @/lib/date are configured in vite.config.ts, next.config.js or a webpack resolve.alias. The compiler reads none of those files, so it treats @/lib/date as a package name, looks for it in node_modules, and gives up.

// ❌ Broken
import { formatDate } from "@/lib/date"
//                         ~~~~~~~~~~~~ Error: Cannot find module '@/lib/date' or its corresponding type declarations.
 
export const placedLabel = formatDate(new Date())
// ✅ Fixed — mirror the alias in tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

Since TypeScript 4.1 you can use paths without baseUrl, in which case the mappings resolve relative to the tsconfig.json itself. Either way the two configs are now duplicated knowledge — when you move src/, update both.

3. Importing a Non-Code Asset

SVGs, CSS modules, images and .vue files are meaningful to your bundler, which rewrites them into a URL or a class-name object at build time. To TypeScript they are just unresolvable specifiers.

// ❌ Broken
import logo from "./assets/logo.svg"
//               ~~~~~~~~~~~~~~~~~~~ Error: Cannot find module './assets/logo.svg' or its corresponding type declarations.
import styles from "./components/Card.module.scss"
//                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Error: Cannot find module './components/Card.module.scss' or its corresponding type declarations.
 
export const brandMark: string = logo
export const cardClass = styles.card
// ✅ Fixed — declare the shape those extensions have at runtime
// src/types/assets.d.ts — must be covered by "include" in tsconfig.json
declare module "*.svg" {
  const src: string
  export default src
}
 
declare module "*.module.scss" {
  const classes: Record<string, string>
  export default classes
}

Two things to watch. First, the declaration file has to be inside the project's include globs, or the compiler never loads it and nothing changes. Second, if you are on Vite you get these declarations for free: add /// <reference types="vite/client" /> to a source file or "types": ["vite/client"] to your compiler options instead of hand-writing them.

4. A Relative Path That Is Wrong by One Character

After a rename or a file move, the specifier and the filename drift apart. A missing plural, a swapped folder, or a capital letter is enough.

// ❌ Broken — the file on disk is services/order.ts
import { fetchOrders } from "./services/orders"
//                          ~~~~~~~~~~~~~~~~~~~ Error: Cannot find module './services/orders' or its corresponding type declarations.
 
export const orderIds = fetchOrders()
// ✅ Fixed — match the filename exactly
import { fetchOrders } from "./services/order"
 
export const orderIds = fetchOrders()

Casing deserves special attention. On macOS and Windows the filesystem is case-insensitive, so ./Utils happily resolves to utils.ts on your laptop and then fails in CI on Linux. Turning on forceConsistentCasingInFileNames (the default since TypeScript 4.7) makes the compiler flag the mismatch locally instead of letting it reach the build server.

How to Fix It

  1. Ask the compiler what it tried. npx tsc --traceResolution prints every candidate path for every specifier. Pipe it through grep for the module that is failing — the log usually ends with the exact directory you expected to contain the file, which tells you immediately whether the problem is the path or the package.

  2. Install the dependency, then its types. If the specifier is a package name, install it in the workspace that imports it. If it ships no declarations, add @types/<name>. If neither exists, you are looking at an untyped library and TS7016 is your next stop rather than TS2307.

  3. Mirror bundler aliases into paths. Every alias your bundler, test runner or dev server defines needs an equivalent under compilerOptions.paths. Note that a Jest moduleNameMapper entry does not satisfy ts-jest's type check — that still reads paths.

  4. Declare ambient modules for anything that is not TypeScript. One src/types/*.d.ts file with declare module "*.svg" style blocks covers all your asset imports, as long as include picks it up.

  5. Fix the path, not the config. Before reaching for paths or rootDirs, check the boring possibilities: a typo, a stale plural, the wrong number of ../ segments, a case mismatch. This is the cause more often than config is.

  6. Treat declare module "some-package" as a last resort. A bare declaration with no body silences the error by typing the whole module as any, which throws away every guarantee you installed TypeScript for. If you must ship it to unblock a release, leave a comment with a link to the upstream issue so it does not become permanent. Keeping forceConsistentCasingInFileNames on and adding real types is what stops this error from coming back.

FAQ

What causes TypeScript error TS2307?

TS2307 fires when module resolution fails: the compiler followed its configured strategy for the string in your import and found neither a source file nor any type declarations at the end of it. The four usual triggers are a package that was never installed (or installed in a sibling workspace), a path alias that exists only in your bundler config, an asset import such as ./logo.svg or ./Card.module.scss, and a relative path that no longer matches the file on disk. Because it is a resolution error rather than a type error, the fix is almost always a change to tsconfig.json, to node_modules, or to the specifier itself.

How do I fix "Cannot find module '@/...'" when the alias works in Vite or Next?

Your bundler resolves @/ from its own configuration, and tsc never reads that file. Add the same mapping to tsconfig.json:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": { "@/*": ["./src/*"] }
  }
}

From TypeScript 4.1 onward baseUrl is optional and the mappings are resolved against the config file's own directory. Restart the TS server in your editor afterwards, because the language service caches the old resolution.

How do I import SVG or CSS modules without TS2307?

Write an ambient declaration for each extension you import and place it in a .d.ts file that your include globs cover — for example declare module "*.svg" { const src: string; export default src }. Use Record<string, string> as the default export for CSS module files so styles.card type-checks. Vite users can skip all of this with /// <reference types="vite/client" />, and framework starters from Next.js or Create React App already ship an equivalent declaration file. What does not work is adding the extension to your bundler config only; the compiler needs the declaration.

Practice This

Browse all TypeScript practice challenges to keep sharpening your type-level skills.

Share this reference

Become a TypeScript Pro

Track your progress through 100+ hands-on challenges. Free, sign in with GitHub.

Or start solving right away: explore all TypeScript challenges