Skip to content
TypeScript ts modules 4 min read

Type-Only Imports & Exports

Types exist only at compile time — they are erased before your code runs. When you import something used purely as a type, TypeScript can drop that import entirely from the emitted JavaScript. The import type and export type syntax makes this intent explicit, prevents accidental runtime dependencies, and avoids subtle bugs with side-effecting modules and transpilers like esbuild and SWC. This page covers type-only imports and exports, inline type modifiers, and the verbatimModuleSyntax option that ties it all together.

The problem type-only imports solve

A plain import of something used only as a type may or may not survive compilation, depending on settings. Tools that transpile files in isolation (esbuild, Babel, SWC) cannot see other files’ types, so they cannot know an import is type-only — and may keep or drop it incorrectly.

import { User } from "./models.js"; // is this a value or a type?

function greet(u: User): string {   // used only as a type
  return `Hi ${u.name}`;
}

Marking the import as type-only removes all ambiguity and guarantees it is erased.

import type and export type

Prefix the statement with type to declare it carries types only. The compiler guarantees the import emits no runtime code, and it errors if you try to use the binding as a value.

import type { User } from "./models.js";
export type { Config } from "./config.js";

const u: User = { name: "Ada" };

// ❌ Error: 'User' cannot be used as a value because it was imported using 'import type'.
new User();

export type { ... } does the same for re-exports, keeping a barrel file’s type re-exports out of the runtime graph.

Inline type modifiers

When a single statement mixes values and types, you do not need two separate imports. Apply type to individual specifiers inline (TypeScript 4.5+).

import { createUser, type User, type Role } from "./users.js";

const role: Role = "admin";
const u: User = createUser("Grace", role);

The same inline modifier works on exports:

export { renderTable, type TableProps } from "./table.js";

This keeps related imports together while still erasing the type-only parts.

verbatimModuleSyntax

The verbatimModuleSyntax compiler option (TS 5.0+, replacing the older importsNotUsedAsValues and isolatedModules emit behavior) enforces a simple rule: any import or export not marked type is emitted verbatim, and any import type is fully erased. This makes the output predictable and isolated-transpiler-safe.

// tsconfig.json
{
  "compilerOptions": {
    "verbatimModuleSyntax": true
  }
}

With it on, you must use import type for type-only imports, or the import is kept at runtime:

// with verbatimModuleSyntax: true
import { User } from "./models.js";      // KEPT at runtime (may error if no runtime export)
import type { User } from "./models.js"; // correctly erased

verbatimModuleSyntax also forbids mixing CommonJS export = with ESM syntax in the same file. It is the recommended setting for new ESM projects and is enabled by many starter templates.

Behavior comparison

SyntaxEmitted to JS?Can use as value?Use when
import { X }Yes (if used)YesX is a value (function, class, const)
import type { X }NeverNoX is used only in type positions
import { type X, y }Only yX no, y yesOne statement mixes both
export type { X }Non/aRe-exporting a type from a barrel

Why it matters for bundlers

Isolated transpilers compile one file at a time without the whole type graph. If a type-only import is not marked, the transpiler may emit import "./models.js", pulling in a module for its side effects and bloating the bundle — or it may error because no runtime export exists. Explicit import type removes the guesswork and keeps builds correct and lean.

// safe across tsc, esbuild, swc, babel
import type { Theme } from "./theme.js";
import { applyTheme } from "./theme.js";

Best Practices

  • Enable verbatimModuleSyntax: true in new projects for predictable, transpiler-safe emit.
  • Use import type whenever a binding is used only in type positions.
  • Prefer inline type modifiers to keep related value and type imports in one statement.
  • Use export type for type re-exports in barrel files to keep them out of the runtime graph.
  • Never call or instantiate a binding imported with import type — it does not exist at runtime.
  • Treat type-only syntax as essential when building with esbuild, SWC, or Babel.
Last updated June 29, 2026
Was this helpful?