verbatimModuleSyntax: Controlling Import/Export Behavior Explicitly
AI generated
type
TypeScript
verbatimModuleSyntax
import and export behavior without compiler magic

No more silently optimized-away imports: with verbatimModuleSyntax, the output contains exactly what the source code wrote.

8 min read TypeScript 5.x Module system

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.

11. FAQ: verbatimModuleSyntax

1Do I have to enable verbatimModuleSyntax in every new project?
It is not mandatory, but for new projects using modern build tools like Vite it is the recommended default, because it rules out whole classes of build errors from the start.
2Can I enable importsNotUsedAsValues and verbatimModuleSyntax at the same time?
No, TypeScript reports a configuration error, because both flags solve the same problem in incompatible ways. The old flags must be removed before enabling the new one.
3Does verbatimModuleSyntax break existing export = statements?
Only for targets with pure ESM output. With CommonJS-compatible module settings, the old syntax remains allowed.
4How does this differ from isolatedModules in practice?
isolatedModules only prevents code that would break under single-file transpilation. verbatimModuleSyntax goes further and additionally forces import/export syntax to appear in the output exactly as written.
5Is there an automated codemod for migration?
The consistent-type-imports ESLint rule from the typescript-eslint project offers an autofix that adds most required type modifiers automatically before the flag itself is enabled.
6Does the flag affect the runtime size of the bundle?
Indirectly yes, because type-only imports are guaranteed to be fully removed, leaving no unnecessary module references in the bundle, which slims down the output especially for large type-definition modules.
7Does verbatimModuleSyntax work with older Node.js CommonJS projects?
Yes, as long as module in tsconfig is set to a CommonJS-compatible format, export = and import = remain usable.
8Do I always have to write type explicitly for an interface import?
Yes, once the flag is active, importing an interface without the type modifier triggers a compiler error, regardless of whether the interface also exists as a value elsewhere in the same module.
9Can verbatimModuleSyntax accidentally remove side-effect imports?
No, that is exactly the problem it solves: pure side-effect imports with no named symbols are guaranteed to remain, regardless of any type inference.
10Is verbatimModuleSyntax part of strict mode?
No, it is a standalone flag and is not automatically enabled by strict: true. It must be set explicitly in tsconfig.