This TypeScript error occurs when using CommonJS-style import assignment syntax (`import x = require('module')`) while targeting ECMAScript modules (ESM). The fix involves switching to ES module syntax (`import x from 'module'`) or adjusting your TypeScript configuration to target CommonJS modules.
The "Import assignment cannot be used when targeting ECMAScript modules" error (TS1202) appears when you try to use TypeScript's CommonJS-style import assignment syntax in a context that targets ECMAScript modules (ESM). TypeScript supports two main module syntaxes: 1. **CommonJS syntax**: Uses `import x = require("module")` and `export =` for compatibility with Node.js's traditional module system 2. **ES Module syntax**: Uses `import x from "module"` and `export default` following the ECMAScript standard When your TypeScript configuration targets ES modules (via the `module` compiler option set to `es2015`, `es2020`, `es2022`, or `esnext`), you must use ES module syntax throughout your code. The CommonJS import assignment syntax is not valid in ES modules because it doesn't exist in the ECMAScript specification. This error commonly occurs when: - Migrating a project from CommonJS to ES modules - Using mixed syntax in a project targeting ES modules - Copying code from older TypeScript examples or tutorials - Working with libraries that export using `export =` syntax in an ES module context
Replace all instances of CommonJS import assignment with ES module syntax:
// BEFORE - CommonJS import assignment (causes error)
import fs = require("fs");
import express = require("express");
import * as path from "path"; // This is actually ES module syntax
// AFTER - ES module syntax (correct for ES modules)
import fs from "fs";
import express from "express";
import * as path from "path"; // This is already correct
// For default exports:
import React from "react";
import lodash from "lodash";
// For named exports:
import { useState } from "react";
import { debounce } from "lodash";
// For namespace imports (already ES module syntax):
import * as fs from "fs"; // Alternative to default importAfter making these changes, recompile your TypeScript code:
npx tsc
# or
npm run buildThe error should be resolved if you're targeting ES modules.
Verify your TypeScript configuration and ensure consistency:
// tsconfig.json - For ES modules (modern projects)
{
"compilerOptions": {
"module": "es2022", // or "esnext", "es2020", "es2015"
"moduleResolution": "node16", // or "bundler", "node"
"target": "es2022",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
}
}If you need to use CommonJS import assignment syntax, change to CommonJS target:
// tsconfig.json - For CommonJS (Node.js compatibility)
{
"compilerOptions": {
"module": "commonjs", // Allows import x = require()
"moduleResolution": "node",
"target": "es2022",
"esModuleInterop": true
}
}After changing tsconfig.json, restart your TypeScript server/IDE.
Some older libraries use export = syntax. TypeScript provides special handling for these:
// Library uses: export = MyClass
// In CommonJS projects:
import MyClass = require("old-library");
// In ES module projects, use esModuleInterop:
import MyClass from "old-library";
// Requires: "esModuleInterop": true in tsconfig.json
// Alternative: Use a namespace import
import * as OldLibrary from "old-library";
const MyClass = OldLibrary.default || OldLibrary;Enable esModuleInterop in your tsconfig.json:
{
"compilerOptions": {
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
}
}This allows ES module syntax to work with CommonJS modules that use export =.
If targeting ES modules, ensure your package.json is configured correctly:
// package.json - For ES modules
{
"type": "module", // Tells Node.js to treat .js files as ES modules
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
},
"scripts": {
"build": "tsc",
"start": "node dist/index.js"
}
}For mixed modules (both .js and .cjs files):
{
"type": "module", // .js files are ES modules
// .cjs files are CommonJS modules
}File extensions matter in ES modules:
- .mjs - ES module (always)
- .cjs - CommonJS module (always)
- .js - Depends on "type" field in package.json
If you need conditional imports, use dynamic import() instead of require():
// BEFORE - CommonJS conditional require (won't work in ES modules)
let module;
if (condition) {
module = require("module-a");
} else {
module = require("module-b");
}
// AFTER - ES module dynamic import
let module;
if (condition) {
module = await import("module-a");
} else {
module = await import("module-b");
}
// For synchronous-like behavior with top-level await:
const module = condition
? await import("module-a")
: await import("module-b");Note: import() returns a Promise, so you need async/ await or .then().
Dynamic imports work in both ES modules and CommonJS (when targeting ES2015+).
When migrating from CommonJS to ES modules, update all files consistently:
# 1. Update tsconfig.json
{
"compilerOptions": {
"module": "es2022",
"moduleResolution": "node16"
}
}
# 2. Update package.json
{
"type": "module"
}
# 3. Convert all import statements
# Use a script or search/replace:
sed -i 's/import \(.*\) = require("\(.*\)")/import \1 from "\2"/g' src/**/*.ts
# 4. Update export statements
# Change:
# export = MyClass
# To:
# export default MyClass
# 5. Add .js extensions to relative imports (for Node16/NodeNext)
import { helper } from "./helper.js"; // Not "./helper"
# 6. Test thoroughly
npm run build
npm testConsider using tools like ts-migrate or @typescript-eslint plugin to automate migration.
### TypeScript Module Target Compatibility
TypeScript's module compiler option determines the module syntax in emitted JavaScript:
| Module Target | Output Syntax | Import Assignment Allowed |
|---------------|---------------|---------------------------|
| commonjs | require()/module.exports | ✅ Yes |
| amd | AMD define() | ✅ Yes |
| umd | UMD wrapper | ✅ Yes |
| system | SystemJS | ✅ Yes |
| es2015/es2020/es2022/esnext | import/export | ❌ No |
| node16/node18/nodenext | Mixed (depends on file extension) | Only in .cjs/.cts files |
### File Extension Rules with Node16/NodeNext
When using --module node16/node18/nodenext, TypeScript treats files differently based on extensions:
- .mts/.mjs: ES modules - cannot use import x = require()
- .cts/.cjs: CommonJS modules - can use import x = require()
- .ts/.js: Depends on nearest package.json "type" field
Example with mixed extensions:
// index.mts (ES module)
import fs from "fs"; // ✅
import fs = require("fs"); // ❌ Error
// utils.cts (CommonJS module)
import fs = require("fs"); // ✅
import fs from "fs"; // ✅ with esModuleInterop### Historical Context
The import x = require() syntax was introduced in TypeScript to provide a 1:1 mapping between TypeScript and CommonJS output. Before ES modules were widely supported, this was the primary way to import CommonJS modules in TypeScript.
With the adoption of ES modules as the JavaScript standard, TypeScript now encourages using ES module syntax (import/export) for new projects. The esModuleInterop flag bridges the gap between CommonJS and ES module interoperability.
### Module Resolution Differences
ES modules and CommonJS have different resolution algorithms:
CommonJS:
- Looks for node_modules from current directory up to root
- Can omit file extensions (tries .js, .json, .node)
- Case-insensitive on Windows
ES Modules:
- Requires file extensions for relative imports (except with bundlers)
- Uses import.meta.resolve() for programmatic resolution
- Follows package.json "exports" field strictly
### Testing Module Compatibility
To test if your code works in both systems during migration:
# Build as ES modules
npx tsc --module es2022 --outDir dist-esm
# Build as CommonJS
npx tsc --module commonjs --outDir dist-cjs
# Test both outputs
node dist-esm/index.js
node dist-cjs/index.js### Tools for Migration
- ts-migrate: Facebook's tool for TypeScript migration
- @typescript-eslint: ESLint rules to enforce module consistency
- jscodeshift: Codemod framework for large-scale refactoring
- tsc --noEmit: Type check without building to catch errors early
### Common Pitfalls
1. Circular dependencies: ES modules handle these differently than CommonJS
2. Dynamic require: require(someVariable) must become import(someVariable)
3. __dirname/`__filename`: Use import.meta.url and fileURLToPath() in ES modules
4. require.cache: No direct equivalent in ES modules
5. Conditional imports: Must use dynamic import() in ES modules
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