verbatimModuleSyntax
Emits every import and export exactly as written unless it carries a type modifier.
"verbatimModuleSyntax": boolean- Added in TypeScript 5.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$trueoptional | boolean | Any import or export without a `type` modifier is emitted verbatim; anything marked `type` is dropped entirely. ES import/export are never rewritten to require() based on the module setting — use import = require() and export = for CommonJS. Implies isolatedModules. |
$falseoptional | boolean | Default. TypeScript performs import elision, removing imports it determines are only used for types. |
Return value
Disables import elision: imports and exports without a type modifier survive into the output, and those with one are erased.
Errors
Errors when an ES import or export appears in a file that would be emitted as CommonJS (error TS1286/TS1287), since the syntax cannot be preserved verbatim there. Also errors on a value import used only as a type when the emit format demands the distinction.
Changelog
- PHP 5.0Introduced, deprecating importsNotUsedAsValues and preserveValueImports, whose combined edge cases it replaces with one rule.
- PHP 5.0Implies isolatedModules, because verbatim emit is exactly what a single-file transpiler can reproduce.
By default TypeScript performs import elision: it inspects every import, works out whether the binding is used as a value anywhere, and quietly deletes the import if it is not. That requires whole-program type information, and it produces surprises — a module whose side effects you relied on vanishes because you only used its types.
The one rule
verbatimModuleSyntax replaces all of that with: anything without a type modifier stays, anything with one goes. What you see is what you get.
// Erased away entirely.
import type { A } from "a";
// Rewritten to 'import { b } from "bcd";'
import { b, type c, type d } from "bcd";
// Rewritten to 'import {} from "xyz";'
import { type xyz } from "xyz";That last line matters: the bare import {} from "xyz" survives, so the module is still loaded and its side effects still run.
The CommonJS restriction
Because the syntax is preserved literally, ES import/export are no longer rewritten into require calls for a CommonJS target — you get an error instead. To emit CommonJS you must write TypeScript's pre-ES2015 syntax, import foo = require("foo") and export = { … }. That is deliberate: it stops you accidentally shipping CommonJS because a package.json lacked "type": "module".
Example
// tsconfig.json
// { "compilerOptions": { "verbatimModuleSyntax": true, "module": "esnext" } }
import { type Cents, formatCents } from "./money.js";
import type { Currency } from "./currency.js";
// Emitted verbatim, minus the type-marked parts:
// import { formatCents } from "./money.js";
// (the `import type` line disappears completely)
const amount: Cents = 1999;
const code: Currency = "USD";
console.log(`${code} ${formatCents(amount)}`);
// -> USD $19.99When to use it
- Guaranteeing that esbuild, swc or Babel produce byte-identical output to tsc.
- Keeping a side-effect-only import from being elided because it looked type-only.
- Migrating off the deprecated importsNotUsedAsValues and preserveValueImports flags.
- Making the ESM/CommonJS boundary explicit so a missing "type": "module" fails loudly.
Frequently asked questions
What does verbatimModuleSyntax do?
It turns off import elision: imports and exports without a type modifier are emitted exactly as written, and those marked type are removed entirely. This makes tsc's output match what single-file transpilers such as esbuild and Babel produce.
Should I use verbatimModuleSyntax or importsNotUsedAsValues?
Use verbatimModuleSyntax — importsNotUsedAsValues and preserveValueImports were deprecated in TypeScript 5.0 in its favour. It replaces their overlapping, hard-to-reason-about behaviour with a single rule.
Why do I get an error about ES imports in a CommonJS file?
Because verbatimModuleSyntax will not rewrite import/export into require calls, so ES syntax is rejected in a file that emits CommonJS. Either mark the package as "type": "module", or write import foo = require("foo") and export = instead.
More examples
Side effects survive an all-type import
With elision, an import whose bindings are all types is deleted, taking the module's side effects with it — a classic cause of "works in dev, breaks in the bundle". Verbatim emit leaves an empty import behind, so the module is still evaluated.
// ./register.ts registers a global polyfill AND exports a type.
import { type Handler } from "./register.js";
const handler: Handler = (msg) => console.log(`handled: ${msg}`);
handler("ping");
// verbatimModuleSyntax: true -> import {} from "./register.js";
// module still runs. ✔
// verbatimModuleSyntax: false -> import elided entirely;
// the polyfill never registers. ✘
// -> handled: pingEmitting CommonJS under the flag
verbatimModuleSyntax refuses to silently convert import/export into require/module.exports, so CommonJS must be written as CommonJS. The payoff is that a file's emitted module format is always visible from its own source.
// A .cts file, or a package without "type": "module".
// ES syntax here would be an error, so use the pre-ES2015 forms:
import foo = require("foo");
function parse(input: string) { return input.trim(); }
function format(input: string) { return input.toUpperCase(); }
export = { parse, format };
// Emitted:
// const foo = require("foo");
// function parse(input) { return input.trim(); }
// function format(input) { return input.toUpperCase(); }
// module.exports = { parse, format };
console.log(format(parse(" ok ")));
// -> OK
Discussion