This TypeScript configuration error occurs when esModuleInterop or allowSyntheticDefaultImports are enabled without specifying a moduleResolution strategy. The fix is to add moduleResolution to your tsconfig.json, typically set to 'node', 'node16', or 'bundler' depending on your build environment.
This error appears when you enable the esModuleInterop or allowSyntheticDefaultImports compiler options in tsconfig.json without also specifying a moduleResolution strategy. These interoperability options control how TypeScript handles imports between CommonJS and ES modules, but they require TypeScript to know how to resolve module paths. The esModuleInterop option enables better compatibility between CommonJS modules (require/module.exports) and ES modules (import/export syntax), emitting helper code to make default imports work correctly with CommonJS modules. The allowSyntheticDefaultImports option allows you to use default import syntax for modules that don't have a default export, but only affects type checking without changing emitted code. Since both options fundamentally change how TypeScript interprets imports, the compiler requires an explicit moduleResolution strategy to determine where to find modules and what format they use. Without this setting, TypeScript cannot properly analyze module dependencies or emit correct interop code.
The most common fix is to add a moduleResolution setting that matches your environment. For Node.js projects:
{
"compilerOptions": {
"esModuleInterop": true,
"moduleResolution": "node",
"module": "commonjs"
}
}For modern Node.js (v16+) using ES modules:
{
"compilerOptions": {
"esModuleInterop": true,
"moduleResolution": "node16",
"module": "node16"
}
}For projects using bundlers (webpack, Vite, esbuild):
{
"compilerOptions": {
"esModuleInterop": true,
"moduleResolution": "bundler",
"module": "esnext"
}
}After adding moduleResolution, run the TypeScript compiler:
npx tsc --noEmit
# or
npm run buildThe error should disappear, and TypeScript will properly resolve your imports.
Ensure your module and moduleResolution settings work together. Here are recommended combinations:
For CommonJS (traditional Node.js):
{
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "node",
"esModuleInterop": true
}
}For ES Modules with Node.js:
{
"compilerOptions": {
"module": "node16",
"moduleResolution": "node16",
"esModuleInterop": true // Implied as true with node16
}
}For bundled frontend apps:
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "bundler",
"esModuleInterop": true
}
}Note that with node16, nodenext, and bundler module resolution, esModuleInterop defaults to true and doesn't need to be explicitly set.
To avoid configuration errors, use official TypeScript base configs:
# Install base configs
npm install --save-dev @tsconfig/node18
# or
npm install --save-dev @tsconfig/recommendedThen extend it in your tsconfig.json:
{
"extends": "@tsconfig/node18/tsconfig.json",
"compilerOptions": {
// Your overrides here
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}Available bases:
- @tsconfig/node18 - Node.js 18 LTS
- @tsconfig/node20 - Node.js 20 LTS
- @tsconfig/recommended - General purpose
- @tsconfig/next - Next.js projects
- @tsconfig/react-native - React Native
- @tsconfig/vite-react - Vite + React
These bases include proper moduleResolution settings by default.
After updating tsconfig.json, restart the TypeScript language server to apply changes:
VS Code:
Press Cmd/Ctrl + Shift + P, then select "TypeScript: Restart TS Server"
Or use the command palette shortcut:
> TypeScript: Restart TS ServerWebStorm/IntelliJ:
File > Invalidate Caches > select "Clear file system cache and Local History" > Invalidate and Restart
Command line:
# Kill any running TypeScript processes
pkill -f tsc
pkill -f tsserver
# Rebuild from scratch
rm -rf dist
npx tscThis ensures your IDE picks up the new configuration immediately.
If you're only using esModuleInterop, you can remove allowSyntheticDefaultImports entirely:
{
"compilerOptions": {
// esModuleInterop implies allowSyntheticDefaultImports
"esModuleInterop": true,
"moduleResolution": "node"
// No need for allowSyntheticDefaultImports
}
}The esModuleInterop option automatically enables allowSyntheticDefaultImports, so setting both is redundant.
If you're not using esModuleInterop and only want type checking without runtime helpers:
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"moduleResolution": "node",
// esModuleInterop: false (default)
}
}However, for most projects, using esModuleInterop: true is recommended as it provides both type checking and correct runtime behavior.
### Understanding esModuleInterop vs allowSyntheticDefaultImports
These two options solve different but related problems:
allowSyntheticDefaultImports (type checking only):
This option tells TypeScript to allow default imports from modules that don't have a default export. It only affects TypeScript's type checker - no JavaScript code is changed:
// CommonJS module (lodash)
// module.exports = { map, filter, reduce, ... }
// With allowSyntheticDefaultImports: true
import _ from "lodash"; // Type checking allows this
// But emits: const _ = require("lodash"); // Won't work at runtime!esModuleInterop (runtime code generation):
This option emits additional helper code to make default imports work correctly with CommonJS modules:
import _ from "lodash";
// Emits:
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
const lodash_1 = __importDefault(require("lodash"));
// Now lodash_1.default works correctlySetting esModuleInterop: true automatically enables allowSyntheticDefaultImports and generates the runtime helpers.
### Module Resolution Strategies Explained
TypeScript supports several moduleResolution strategies:
classic - Legacy strategy, rarely used today. Doesn't support node_modules.
node - Traditional Node.js CommonJS resolution:
- Looks in node_modules
- Supports package.json "main" field
- Default for module: "commonjs"
node16/nodenext - Modern Node.js with ES module support:
- Respects package.json "type": "module"
- Supports "exports" field
- Requires .js extensions in relative imports
- Auto-enables esModuleInterop
bundler - For Vite, webpack, esbuild:
- Supports package.json "imports" and "exports"
- Never requires file extensions
- Assumes bundler handles module resolution
- Auto-enables esModuleInterop
### When to Use Each Configuration
Building a library for npm:
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "bundler",
"esModuleInterop": true,
"declaration": true
}
}Node.js backend (CommonJS):
{
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "node",
"esModuleInterop": true,
"target": "ES2020"
}
}Node.js backend (ES modules):
{
"compilerOptions": {
"module": "node16",
"moduleResolution": "node16",
"target": "ES2022"
}
}Note: With node16, you must use .js extensions in relative imports even though source files are .ts:
// WRONG
import { foo } from "./utils/helper";
// CORRECT
import { foo } from "./utils/helper.js";React frontend (bundled):
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"esModuleInterop": true
}
}### Why This Requirement Exists
In older TypeScript versions, you could set esModuleInterop without moduleResolution, leading to ambiguous behavior. Modern TypeScript enforces that these interop options must be paired with an explicit resolution strategy because:
1. The interop code generated depends on how modules are resolved
2. Different resolution strategies have different rules (e.g., file extensions)
3. Type checking behavior differs based on resolution mode
4. It prevents misconfigured projects that compile but fail at runtime
### Checking Your Configuration
Verify your current configuration:
npx tsc --showConfigThis outputs the final resolved configuration including defaults, helping you see exactly what TypeScript is using.
Function expression requires a return type
Function expression requires a return type
Value of type 'string | undefined' is not iterable
How to fix "Value is not iterable" in TypeScript
Type 'undefined' is not assignable to type 'string'
How to fix "Type undefined is not assignable to type string" in TypeScript
Type narrowing from typeof check produces 'never'
How to fix "Type narrowing produces never" in TypeScript
Type parameter 'T' has conflicting constraints
How to fix "Type parameter has conflicting constraints" in TypeScript