allowSyntheticDefaultImports

Allows default imports from modules with no default export, without changing emit.

Signature
"allowSyntheticDefaultImports": boolean
  • Added in TypeScript 1.8
  • Deprecated since PHP 6.0 (only the false setting; the flag itself remains)

Parameters

ParameterTypeDescription
$trueoptionalbooleanThe checker pretends any module lacking a default export also declares `module.exports.default = module.exports`, so `import x from "./mod"` type checks. Emitted JavaScript is unchanged.
$falseoptionalbooleanA default import of a module with no default export is an error. Deprecated in TypeScript 6.0.

Return value

Relaxes type checking so a module without an explicit default export can still be default-imported. Emits nothing.

Errors

With the flag off, a default import of a module that has no default export is error TS1192 ("Module has no default export") or TS1259 when esModuleInterop would fix it.

Changelog

  • PHP 2.7Enabling esModuleInterop began implying this flag, so most projects never set it directly.
  • PHP 4.7Defaults to true when module is node16 or nodenext (later also preserve), following the esModuleInterop default.
  • PHP 6.0Can no longer be set to false; the permissive interop checking is always on.

allowSyntheticDefaultImports is the type-checking half of the CommonJS interop story. It lets you write import React from "react" against a module that never declares a default export, by having the checker behave as if the module ended with module.exports.default = module.exports.

The crucial limitation

It emits nothing. Not a helper, not a wrapper, not a byte of difference in the output. It only silences the error. That is exactly what you want when some other tool performs the interop at runtime — Babel, esbuild, Vite, webpack, or a noEmit setup where tsc is purely a checker — and exactly what you do not want when tsc itself produces the JavaScript you ship.

When to reach for it

Almost never on its own. Enabling esModuleInterop turns this on for you and emits the __importDefault helper that makes the import work at runtime. Set allowSyntheticDefaultImports alone only when a bundler owns the emit and you want checking to match its more forgiving behaviour.

What trips people up

Turning it on by itself, with tsc emitting CommonJS, produces a clean build and a runtime crash: the output still reads require("x").default, which is undefined. The error you removed was telling the truth.

Example

Example · typescript
// tsconfig.json
//   { "compilerOptions": { "allowSyntheticDefaultImports": true, "noEmit": true } }
// A bundler emits the JavaScript; tsc only checks.

// utilFunctions.js — CommonJS, no `default` export
//   const getStringLength = (str) => str.length;
//   module.exports = { getStringLength };

import utils from "./utilFunctions";

const count = utils.getStringLength("Check JS");
console.log(count);
// -> 8
// Without the flag this line is error TS1192: Module has no default export.

When to use it

  • Type checking with noEmit while Vite, esbuild or Babel performs the actual interop.
  • Matching tsc's checking to a bundler that already synthesises default exports.
  • Importing an untyped CommonJS helper file with a default import in a JS-plus-checkJs project.
  • Silencing TS1192 in a project whose build pipeline is not tsc.

Frequently asked questions

What is the difference between allowSyntheticDefaultImports and esModuleInterop?

allowSyntheticDefaultImports only relaxes type checking and emits nothing, while esModuleInterop also emits the __importDefault and __importStar helpers that make the import work at runtime. Enabling esModuleInterop implies allowSyntheticDefaultImports.

Do I need allowSyntheticDefaultImports if I already set esModuleInterop?

No, esModuleInterop turns it on automatically. Setting it explicitly is only useful when you want the relaxed checking without the emit change, which is the case for noEmit projects built by a bundler.

How do I fix "Module has no default export" (TS1192)?

Enable esModuleInterop if tsc emits your JavaScript, or allowSyntheticDefaultImports if a bundler does. Alternatively change the import to a namespace import, or to import x = require("x") in a CommonJS file.

More examples

What the checker pretends the module says

The synthetic default is a fiction maintained only inside the type checker, mirroring what Babel emits for real. Nothing in the output file changes, which is why the flag is safe under a bundler and dangerous when tsc itself emits CommonJS.

Example · typescript
// The real CommonJS module:
//   const getStringLength = (str) => str.length;
//   module.exports = { getStringLength };

// What allowSyntheticDefaultImports makes the checker see:
//   module.exports = allFunctions;
//   module.exports.default = allFunctions;   // synthesised, never emitted

import utils from "./utilFunctions";
import * as alsoUtils from "./utilFunctions";

// Both forms type check, and under a bundler both reach the same object.
console.log(utils.getStringLength("abc"));
console.log(alsoUtils.getStringLength("abcd"));
// -> 3
// -> 4

The flag alone is not enough when tsc emits

This is the bug report the flag generates. Type checking passes because the synthetic default exists for the checker, but the emitted require has no interop wrapper, so .default is undefined. TypeScript 6.0 closes the hole by refusing esModuleInterop: false.

Example · typescript
// tsconfig.json
//   { "module": "commonjs",
//     "allowSyntheticDefaultImports": true,
//     "esModuleInterop": false }        // the trap

import utils from "./utilFunctions";
console.log(utils.getStringLength("Check JS"));

// Compiles with no errors. Emitted output:
//   const utilFunctions_1 = require("./utilFunctions");
//   console.log(utilFunctions_1.default.getStringLength("Check JS"));
//                               ^^^^^^^ undefined at runtime
// -> TypeError: Cannot read properties of undefined (reading 'getStringLength')

// Fix: turn on esModuleInterop, which emits __importDefault.
// -> 8

Discussion

  • Be the first to comment on this lesson.