TypeScript

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.

Mahdi Ben RhoumaMahdi Ben Rhouma
Updated
8 min read

TypeScript rejects an import you're sure is correct:

text
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.

ts
// 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';   // aliased

Cause B — default vs named export mismatch#

Importing a default as named (or vice versa):

ts
// 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';   // correct

So 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").

jsonc
// 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.

Don't
"allowSyntheticDefaultImports": true // silences the type error but emits unchanged, possibly unsafe JS
Do
"esModuleInterop": true // fixes type-checking AND emits the interop shim

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:

ts
import * as React from 'react';   // no default needed

Cause 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):

ts
import type { User } from './types';   // type
import { createUser } from './api';    // value
export type { User };                  // re-export a type

Cause 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.

bash
npm ls foo @types/foo
npm i -D @types/foo@latest   # or pin to match the runtime major
When this won't work
  • module: "nodenext"/node16 changes interop further and can supply a synthetic default without esModuleInterop in 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 seeLikely causeFix
TS2305 on a named importtypo / wrong namematch the export exactly
TS2305 / TS1192default vs namedswitch import X ↔ import { X }
TS1259 / TS2497CJS default importesModuleInterop: true
TS1484 / TS1485type imported as valueimport type
TS2305 on a typed libstale @typesrealign 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"":

jsonc
// 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:

jsonc
{
  "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

|

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

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.