Fix TS2305: Module Has No Exported Member in TypeScript
TS2305 says the export you're importing doesn't exist under that name. The cause is almost always one of: a typo, default-vs-named confusion, a CJS/ESM interop mismatch, or @types drifting from the runtime package. Each has a precise fix.
TypeScript rejects an import you're sure is correct:
Module '"./user-service"' has no exported member 'getUser'. (2305)TS2305 means exactly what it says: the module does not export that name. The cause is almost always one of four things, and each has a precise fix. This guide walks them in order of frequency.
Cause A — wrong name, typo, or rename (most common)#
The named import must match an export exactly — case-sensitive.
// user-service.ts
export function getUser() {}
import { GetUser } from './user-service'; // TS2305 (wrong case)
import { getUser } from './user-service'; // correct
import { getUser as fetchUser } from './user-service'; // aliasedCause B — default vs named export mismatch#
Importing a default as named (or vice versa):
// default export: export default function foo() {}
import { foo } from './m'; // TS2305 — it's the default, not a named "foo"
import foo from './m'; // correct
// named export only: export const bar = 1;
import bar from './m'; // TS1192: has no default export
import { bar } from './m'; // correctSo TS2305 and its sibling TS1192 ("has no default export") are usually the same root: you guessed the wrong export shape. Open the module (or its .d.ts) and check.
Cause C — CommonJS / ESM interop#
A CommonJS package (module.exports = ... / export =) default-imported with interop off throws TS1259 or TS2497 ("can only be default-imported using the 'esModuleInterop' flag").
// tsconfig.json — the fix
{ "compilerOptions": { "esModuleInterop": true } }What esModuleInterop does (from the docs): with it off, import X from "moment" is checked as require("moment").default — which often doesn't exist. Turning it on changes the compiler's checking and emits helper shims (__importDefault, __importStar) so default/namespace imports of CommonJS work safely. It also automatically enables allowSyntheticDefaultImports.
The docs warn directly: "allowSyntheticDefaultImports without esModuleInterop should be avoided. It changes the compiler's checking behavior without changing the code emitted by tsc, allowing potentially unsafe JavaScript to be emitted."
If you can't change config, the namespace import is a safe workaround:
import * as React from 'react'; // no default neededCause D — a type-only export under verbatimModuleSyntax#
Single-file transpilers (Babel, esbuild, SWC) can't tell a type from a value, so under verbatimModuleSyntax you must use import type (errors TS1484/TS1485):
import type { User } from './types'; // type
import { createUser } from './api'; // value
export type { User }; // re-export a typeCause E — stale @types vs the runtime package#
If @types/foo is on a different version than foo, the typed export genuinely isn't in the installed .d.ts — a real TS2305. The source of truth is the .d.ts, not the JS.
npm ls foo @types/foo
npm i -D @types/foo@latest # or pin to match the runtime majormodule: "nodenext"/node16changes interop further and can supply a synthetic default withoutesModuleInteropin some checking modes — verify against the module-resolution docs if you're on those.verbatimModuleSyntax(TS1484/1485) needs TypeScript 5.0+ — older versions use different type-import rules.
Decision guide#
| You see | Likely cause | Fix |
|---|---|---|
| TS2305 on a named import | typo / wrong name | match the export exactly |
| TS2305 / TS1192 | default vs named | switch import X ↔ import { X } |
| TS1259 / TS2497 | CJS default import | esModuleInterop: true |
| TS1484 / TS1485 | type imported as value | import type |
| TS2305 on a typed lib | stale @types | realign versions |
Official references: esModuleInterop, allowSyntheticDefaultImports, ESM/CJS interop handbook, isolatedModules.
Cause F — a broken exports map on the package's own package.json#
Every cause above assumes the package's .d.ts file is reachable and correct — but
modern packages that ship a package.json "exports" field can hide their own type
declarations from TypeScript even when the file exists on disk and the export is real. The same map also decides what Node loads at runtime: when a subpath is missing from it, or listed only under "import" while your code calls require(), Node fails at runtime with ERR_PACKAGE_PATH_NOT_EXPORTED.
The "exports" field is a map of conditions (import, require, types, default, and
so on) that Node and bundlers use to pick which file resolves for a given import
statement. TypeScript reads the same field, under moduleResolution: "bundler" or
"node16"/"nodenext" in tsconfig.json. If you maintain the package, keep the map in
the shape TypeScript documents. The TypeScript 4.7 release notes
state that "the "types" condition should always come first in "exports"":
// the package's own package.json — "types" listed after "import"
{
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
}
}
}That order is not, on its own, a cause of TS2305 on a recent compiler. In a test on
TypeScript 5.9.3 (3 October 2026), import { getUser } from 'lib' compiled cleanly against
the map above, under both bundler and nodenext, and still did when the .d.ts lived in
a separate types/ folder: the compiler fell through to the "types" entry. Put
"types" first anyway, as the documentation asks:
{
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
}
}The mistake that does break imports is a subpath you're importing from
(import { X } from "some-lib/utils") that the map does not list at all.
Node and modern bundlers treat "exports" as an allowlist: any path not explicitly
listed is unreachable, regardless of whether the file physically exists in the published
package. TypeScript reports that as TS2307, not TS2305: in the same TypeScript 5.9.3 test,
importing an unlisted dist/utils subpath failed with Cannot find module ... or its corresponding type declarations under both bundler and nodenext. So if the error in
front of you really is TS2305, causes A to E above are the ones to check.
If you don't control the package, the fastest diagnosis is npm ls <pkg> to confirm the
installed version, then opening node_modules/<pkg>/package.json directly to check
whether "exports" lists a "types" condition at all for the path you're importing — a gap
here means the fix is a package update or an issue upstream, not a change to your own
tsconfig.json. This overlaps with resolution failures covered from the declaration-file
angle in Fix TS7016: Could Not Find a Declaration File for Module,
which walks through moduleResolution settings in more depth.
Further reading: Fix "Property does not exist on Window" in TypeScript. Also see Module resolves to an untyped module: fix TS2665 in .d.ts.
Frequently Asked Questions
Still stuck? Paste your error.
Paste the error message or stack trace — we check it against every fix and tool on the site.
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.
Continue Reading
TS7016: Could not find a declaration file for module (fix)
TS7016 fires when you import an untyped JavaScript module under noImplicitAny. The right fix is usually `npm i -D @types/X` — but sometimes the package already ships types, and sometimes you need to write a one-line declaration.
Module resolves to an untyped module: fix TS2665 in .d.ts
Why a hand-written declaration for an untyped package fails with "cannot be augmented", why the same block works in a .d.ts but not a .ts file under module node16 to nodenext, and the one-line move that fixes it.
Fix "Property does not exist on Window" in TypeScript
Learn how to safely extend the Window interface in TypeScript with declaration merging and declare global, and why bracket notation on an undeclared key fails under noImplicitAny.


