Topics

Jump to a topic.

Search questions

Find a question by title, tag, or summary.

What do import type, isolatedModules, and verbatimModuleSyntax do?

Questions in this chapter (6)
  • Draft
  • medium
  • Askedvery often
  • Verified October 2026

Answer

By default tsc performs import elision: it drops an import (or a named binding) it sees is never used as a value. That works because tsc has the whole program and knows whether User is an interface or a class. A single-file transpiler (Babel, ts.transpileModule, esbuild-style tools) cannot see the other file, so it cannot make that call. The three features exist for that gap.

  • import type / export type (TS 3.8). The declaration is usable only in type positions and is erased completely. Using it as a value is an error (TS1361), so class X extends Imported fails if you imported with import type. The inline form import { type A, b } (TS 4.5) marks single names.
  • isolatedModules. A checking flag. It reports code that cannot be compiled from one file alone, for example re-exporting a type without export type (TS1205). Per the docs it "does not change the behavior" of checking and emitting. It does not turn off elision.
  • verbatimModuleSyntax (TS 5.0). Removes the guesswork: an import or export without type is kept; anything with type is dropped. If you import a type without type, you get TS1484. It replaces importsNotUsedAsValues and preserveValueImports, which TS 5.9 reports as removed.

Consequences of verbatim emit:

  • import { type A } from "./m" becomes import {} from "./m", which keeps the side effects of ./m. Use import type { A } to drop the module entirely.
  • It forbids ESM syntax in a file that compiles to CommonJS (TS1287); use import x = require() and export = there.

Recommendation: enable verbatimModuleSyntax in new projects, because TS 5.9's tsc --init template already does (alongside isolatedModules), and your emit then matches what Node, Babel, or a bundler will do with the source.

Example

// TypeScript 7.0.2 and 5.9.3, --module esnext --verbatimModuleSyntax
// a.ts
console.log("a.ts loaded");
export interface User { name: string }
export class Api {}

// b.ts
import { User } from "./a";
// Error TS1484: 'User' is a type and must be imported using a type-only import when 'verbatimModuleSyntax' is enabled.
import type { Api } from "./a";
class Bad extends Api {}
// Error TS1361: 'Api' cannot be used as a value because it was imported using 'import type'.

// c.ts
import { type User } from "./a";
export const u: User = { name: "Ada" };
// c.js with verbatimModuleSyntax: import {} from "./a";  (a.ts's code runs: "a.ts loaded")
// c.js without it:                (no import; "a.ts loaded" never prints)

Output was checked with tsc on both versions, and c.js was run on Node 24.19.0: it printed a.ts loaded with the flag, and nothing without it.

Pitfalls

  • Believing isolatedModules changes emit. It only adds errors. The one that bites is re-exporting a type: export { User } fails with TS1205 until you write export type { User } (checked on both versions).
  • Treating import { type A } from "x" as a full removal. Under verbatimModuleSyntax it leaves import {} from "x"; use import type to delete it.
  • Combining default and named in one type-only import: import type D, { N } from "m" is TS1363. Split it into two statements.
  • Turning on verbatimModuleSyntax in a CommonJS-output project and being surprised: top-level export of values is TS1287 there (checked on both versions).

Follow-ups

Why did importsNotUsedAsValues and preserveValueImports go away?
The TS 5.0 notes call them deprecated in favour of verbatimModuleSyntax. On 5.9.3 they report TS5102 "has been removed"; TS 7.0.2 reports them as unknown options.