import and export behavior without compiler magic
No more silently optimized-away imports: with verbatimModuleSyntax, the output contains exactly what the source code wrote.
Table of Contents
- 1. The history: from isolatedModules to verbatimModuleSyntax
- 2. What changes concretely: imports and exports are preserved
- 3. import type and export type become mandatory
- 4. Impact on CommonJS interop (esModuleInterop)
- 5. Understanding and fixing error messages
- 6. Interplay with isolatedModules and importsNotUsedAsValues
- 7. Migrating older TypeScript projects
- 8. Editor support: auto-import with the correct type keyword
- 9. When NOT to enable verbatimModuleSyntax
- 10. Summary
- 11. FAQ
1. The history: from isolatedModules to verbatimModuleSyntax
Before TypeScript 5.0, several separate, sometimes contradictory flags controlled module behavior: isolatedModules, importsNotUsedAsValues, and preserveValueImports. Each solved part of the same underlying problem, but the combination was hard to predict.
verbatimModuleSyntax consolidates these three flags into a single, clear rule: everything written as an import or export in the source stays in the compiled output, unless it is explicitly marked with type.
Since its introduction, the TypeScript release notes explicitly recommend replacing the three old flags with the new one, because it achieves the same goals with far less surprising behavior.
For teams that have worked with TypeScript for a long time, this step resembles retiring several older linting rules in favor of a single, clearly documented one: less configuration surface, fewer combinations that could produce contradictory behavior.
2. What changes concretely: imports and exports are preserved
Without verbatimModuleSyntax, tsc by default automatically strips imports used exclusively as types from the JavaScript output. That sounds convenient, but causes surprises when an import was also meant to have a side effect, such as loading CSS or registering a polyfill.
With the flag enabled, only the written syntax decides, not the compiler's type inference, whether an import is preserved: a plain import { Foo } from './foo' stays in the output, an import type { Foo } from './foo' is guaranteed to disappear entirely.
This predictability matters especially for side-effect imports, because previously the compiler had to guess via heuristics whether an import existed only for its types or also for a side effect, which in edge cases led to incorrectly removed imports.
// Without verbatimModuleSyntax: tsc decides what stays.
// With verbatimModuleSyntax: the syntax decides.
import type { User } from "./types"; // always disappears in the output
import { formatUser } from "./format"; // always stays in the output
import "./register-polyfill"; // side effect is guaranteed to remain
3. import type and export type become mandatory
As soon as verbatimModuleSyntax is active, TypeScript reports an error when a symbol used exclusively as a type is imported or re-exported without the type modifier. This is essentially the same rule already known from isolatedModules, but enforced more consistently.
In practice this means mixed imports, pulling both values and types from the same module, must set the type modifier per symbol, for example import { type User, formatUser } from './module', instead of requiring two separate import statements.
This inline syntax for mixed imports was already introduced in TypeScript 4.5 and is the recommended standard approach under verbatimModuleSyntax, because it stays explicit without unnecessarily lengthening the code.
// Mixed import: value and type from the same module
import { type User, formatUser } from "./user";
export function greet(user: User): string {
return formatUser(user);
}
// Re-export also requires the type modifier:
export type { User };
4. Impact on CommonJS interop (esModuleInterop)
verbatimModuleSyntax also changes how TypeScript handles CommonJS interop. Without the flag, a default import from a CommonJS module with no real default export could still work, because tsc synthesized a default export while compiling.
With verbatimModuleSyntax, that synthesis still exists for type checking purposes as long as esModuleInterop is active, but the actual module syntax in the output matches more precisely what the target environment, such as Node.js in ESM mode, genuinely expects.
For projects switching between CommonJS and ESM, or supporting both simultaneously (the dual-package hazard), this precision is the difference between a build that actually runs in both environments and one that only works in one of them.
5. Understanding and fixing error messages
The typical error states, in effect, that a symbol is used only as a type and therefore must be imported with type, given verbatimModuleSyntax is active. Content-wise it is almost identical to the isolatedModules message, but phrased more strictly.
A second common error involves export = and import =, the old CommonJS interop syntax: under verbatimModuleSyntax, this syntax is only allowed when the module system targets CommonJS-compatible output formats; it is rejected for pure ESM targets.
Most modern editors with a TypeScript language server offer an automatic quick fix for both error classes that rewrites the import line correctly, so migration rarely requires manually searching through every file.
6. Interplay with isolatedModules and importsNotUsedAsValues
TypeScript 5.0 officially deprecates importsNotUsedAsValues and preserveValueImports in favor of verbatimModuleSyntax. Enabling either old flag alongside the new one triggers a configuration error, since they conflict.
isolatedModules, on the other hand, may remain active in parallel, though it is redundant, because verbatimModuleSyntax already enforces its rules as a subset. Many project templates still enable both anyway, to explicitly document intent in the code.
For library authors, combining both flags is especially recommended, because it guarantees the codebase behaves identically regardless of the consumer's build tool (tsc, esbuild, SWC).
7. Migrating older TypeScript projects
The first step is removing importsNotUsedAsValues and preserveValueImports from tsconfig, since they cannot coexist with verbatimModuleSyntax. Then the new flag is enabled and tsc --noEmit is run to collect every affected spot.
In large codebases, an automated approach pays off: many ESLint setups offer an autofix via the consistent-type-imports rule from @typescript-eslint, which adds nearly all needed type modifiers before the flag itself is even turned on.
After migration, it is worth keeping the ESLint rule permanently active, so new violations surface right in the editor while writing code, rather than at the next tsc run or, worse, only in the production build.
8. Editor support: auto-import with the correct type keyword
Modern TypeScript language servers detect an enabled verbatimModuleSyntax and adjust auto-generated import suggestions accordingly: when a symbol used exclusively as a type is inserted via auto-import, the editor automatically adds the type modifier.
This behavior significantly reduces migration friction, because developers rarely have to think manually about the correct syntax during daily work, the editor handles the distinction between type and value automatically in the background.
With older editor versions or language server configurations unaware of the flag, incorrectly generated imports can still occur, which is why an up-to-date TypeScript language server should be part of the migration checklist.
9. When NOT to enable verbatimModuleSyntax
In very old codebases with heavy use of namespace-based module systems and global scripts, migration can be disproportionately expensive compared to the actual benefit for a project that will never switch to a single-file transpiler anyway.
The table below compares scenarios where enabling it pays off versus where it should probably be deferred.
| Scenario | Recommendation | Reasoning | Effort |
|---|---|---|---|
| New project with Vite/esbuild | Enable immediately | Prevents whole classes of build errors from day one | None |
| Existing project, tsc-only build | Recommended, not urgent | tsc already compiles correctly | Low to medium |
| Library with many consumers | Enable | Guarantees consistent behavior across all build tools | Medium |
| Legacy codebase with namespace modules | Defer | Migration effort outweighs short-term benefit | High |
Mironsoft
TypeScript migration, type safety, and team onboarding
A JavaScript codebase without type safety, but no time for a full migration?
We migrate existing JavaScript projects to TypeScript step by step, set up strict compiler settings cleanly, and bring teams to the same type-safety level with code reviews and style guides.
Migration Roadmap
Plan and execute a gradual JS-to-TS migration without big-bang risk.
Strict Mode Rollout
Set up tsconfig.json, ESLint rules, and CI checks for lasting type safety.
Team Onboarding
Bring developers up to speed on TypeScript best practices with workshops and reviews.
10. Summary
verbatimModuleSyntax
Core principle
Syntax decides, not type inference, whether an import stays in the output.
Replaces
Parts of isolatedModules, importsNotUsedAsValues, preserveValueImports.
Required syntax
import type / export type for all pure type imports and exports.
Editor support
Auto-import adds the type modifier automatically with a current language server.