The Error
You write a clean default import and TypeScript immediately complains:
Module 'express' can only be default-imported using the 'allowSyntheticDefaultImports' flag
Module 'react' can only be default-imported using the 'allowSyntheticDefaultImports' flag
Module 'lodash' can only be default-imported using the 'allowSyntheticDefaultImports' flag
This tends to surface after adding a new npm package, tightening your TypeScript config, or converting an existing JS project to TypeScript.
Why It Happens
Older CommonJS packages โ think express, lodash, moment โ don't have a real ES module default export. They publish everything through module.exports = .... TypeScript notices the mismatch and refuses to let you write:
import express from 'express'; // โ Error without the flag
The flag allowSyntheticDefaultImports is TypeScript's way of saying: "treat module.exports as if it were a default export." It's a type-checking-only setting โ your compiled JavaScript output stays exactly the same.
One thing worth knowing: enabling esModuleInterop automatically turns on allowSyntheticDefaultImports too. More on that in Fix 2.
Fix 1 โ Enable allowSyntheticDefaultImports in tsconfig.json (Recommended)
Open tsconfig.json and drop the flag into compilerOptions:
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
// ... rest of your options
}
}
Save and re-run tsc. The error disappears. This single-line change fixes the problem in the vast majority of projects.
Fix 2 โ Use esModuleInterop Instead (Better for Node.js/CommonJS Projects)
Think of esModuleInterop as the upgraded version. It does everything allowSyntheticDefaultImports does โ and also injects runtime helpers that correctly wire up CommonJS interop in the emitted JavaScript. For Node.js projects or anything bundling CommonJS packages, reach for this one instead:
{
"compilerOptions": {
"esModuleInterop": true,
// allowSyntheticDefaultImports is automatically true when esModuleInterop is true
}
}
With this in place, default imports just work:
import express from 'express'; // โ
import React from 'react'; // โ
import _ from 'lodash'; // โ
import moment from 'moment'; // โ
Fix 3 โ Use Namespace Import (Without Changing tsconfig)
Can't touch the TypeScript config? Swap to a namespace import instead:
// Instead of:
import express from 'express'; // โ
// Use:
import * as express from 'express'; // โ
It works because the module does export something โ just not as a named default. The namespace import grabs the entire module.exports object. Fair warning though: it looks odd, and some library type definitions assume default import syntax. That mismatch can trigger secondary type errors elsewhere in your code.
Verify the Fix
Run the compiler to confirm everything is clean:
# Using tsc directly
npx tsc --noEmit
# Or if you have a build script
npm run build
In a Vite or CRA project, just save the file. The dev server recompiles automatically, and the red error banner in the browser should vanish within a second or two.
Want to double-check which flags are actually active? Run this:
npx tsc --showConfig | grep -E 'allowSyntheticDefaultImports|esModuleInterop'
You should see:
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
Common Scenario: Create React App / Vite
Both CRA and Vite include esModuleInterop: true in their generated tsconfig.json out of the box. So if you're hitting this error in a brand-new CRA or Vite project, something changed after generation. Either the flag was manually removed, or you're extending a base config that overrides it. Check both files:
{
"extends": "./tsconfig.base.json", // โ check this file too
"compilerOptions": { ... }
}
Common Scenario: Migrating from JavaScript
Renaming .js files to .ts and firing up TypeScript for the first time? Expect this error on the very first compile. Packages like express, lodash, and moment will all trigger it. Add esModuleInterop: true to tsconfig.json as part of your migration setup โ it clears the whole batch in one shot.
Quick Tips
- Prefer
esModuleInterop: trueoverallowSyntheticDefaultImports: truealone โ it's safer for CommonJS interop and is the TypeScript team's recommendation. - Writing a library for others to consume? Be careful with
esModuleInterop. It changes how the emitted JavaScript handles imports, which can surprise consumers who aren't using the same setting. - Some packages (including
@types/node) have been updated to export proper ES defaults. If a well-maintained package still triggers this error, the culprit is almost always your tsconfig, not the type definitions. - Don't mix namespace imports (
import * as X from '...') and default imports (import X from '...') for the same module โ pick one style and stick with it throughout the file.

