Skip to main content
Beta: Front-End Checklist is currently in beta. Some issues are still being fixed. Thanks for your patience.

Use import type for type-only imports

Use the import type syntax for imports that are only needed as TypeScript types, ensuring they are fully erased at compile time with zero runtime cost.

Utilities
Quick take
Typical fix time 10 min
  • import type is erased entirely at compile time — no runtime module evaluation
  • Prevents accidentally importing a value when only a type is needed
  • Reduces circular dependency issues caused by type-only relationships
  • verbatimModuleSyntax in TypeScript 5 enforces this automatically
Why it matters: Regular imports cause the JavaScript runtime to evaluate and execute the imported module even when only a TypeScript type from that module is used. This can increase bundle size, introduce unintended side effects, and create circular dependency issues that are otherwise avoidable. Using import type makes the erased-at-runtime nature of type imports explicit and enables bundlers and the TypeScript compiler to handle them optimally.

Rule Details

TypeScript's import type (opens in new tab) syntax explicitly marks an import as type-only, guaranteeing that the compiler erases it completely from the emitted JavaScript with no module evaluation.

Code Examples

// ❌ Regular import used only for a type — module is evaluated at runtime
import { User } from './user'          // './user' module code runs
import { ApiResponse } from './api'    // './api' module code runs
 
function formatUser(user: User): string {
  return user.name
}
 
async function handleResponse(res: ApiResponse<User>): Promise<User> {
  return res.data
}
// ✅ import type — both imports are completely erased from the output JS
import type { User } from './user'
import type { ApiResponse } from './api'
 
function formatUser(user: User): string {
  return user.name
}
 
async function handleResponse(res: ApiResponse<User>): Promise<User> {
  return res.data
}

The compiled output for the type-only version contains no reference to ./user or ./api. No module evaluation, no bundle inclusion.

Why It Matters

Regular imports cause the JavaScript runtime to evaluate and execute the imported module even when only a TypeScript type from that module is used. This can increase bundle size, introduce unintended side effects, and create circular dependency issues that are otherwise avoidable. Using import type makes the erased-at-runtime nature of type imports explicit and enables bundlers and the TypeScript compiler to handle them optimally.

Mixed Imports — Types and Values Together

When you need both a type and a runtime value from the same module, you can either use a single import with inline type modifiers, or separate the imports:

// Option A — inline type modifier (TypeScript 4.5+)
import { createUser, type User, type CreateUserOptions } from './user'
//                   ^^^^ type  ^^^^ type — erased at compile time
//       ^^^^^^^^^^^ value — kept in runtime output
 
// Option B — two separate imports
import { createUser } from './user'          // runtime value
import type { User, CreateUserOptions } from './user' // types only
 
// Both are correct — choose based on team preference

verbatimModuleSyntax — Automatic Enforcement

TypeScript 5.0 introduced verbatimModuleSyntax, which enforces that type-only imports use import type. The compiler will error if you write a regular import that is only used as a type.

// tsconfig.json
{
  "compilerOptions": {
    "verbatimModuleSyntax": true,
    // verbatimModuleSyntax replaces the older importsNotUsedAsValues and
    // preserveValueImports options — do not set those alongside it
    "module": "ESNext",
    "moduleResolution": "bundler"
  }
}

With verbatimModuleSyntax (opens in new tab) enabled, this becomes a compiler error:

// ❌ TypeScript error: This import is never used as a value and must use 'import type'
import { User } from './user'
 
function greet(user: User): string {
  return `Hello, ${user.name}`
}

ESLint Rule

Add the consistent-type-imports rule (opens in new tab) to enforce import type via your linter, independently of TypeScript version:

// eslint.config.js (flat config)
{
  "rules": {
    "@typescript-eslint/consistent-type-imports": [
      "error",
      {
        "prefer": "type-imports",
        "fixStyle": "inline-type-imports"
      }
    ]
  }
}

The rule is auto-fixable — running eslint --fix converts all violating imports automatically.

Re-exporting Types

Type-only exports follow the same pattern:

// types.ts
export type { User } from './user'
export type { ApiResponse, ApiError } from './api'
 
// Both are erased from the compiled output of types.ts
// Consumers of types.ts still get the full types
Do not use import type for class instances

A TypeScript class declares both a type (the instance shape) and a runtime value (the constructor). If you use import type { MyClass } and then write new MyClass(), you will get a compile error because the constructor was erased. Use a regular import whenever you call new, access static members, or use the class as a value at runtime.

Type Imports in Declaration Files

In .d.ts files, all imports are already type-only by nature, but using import type is still considered good practice as it makes intent explicit:

// declarations.d.ts
import type { EventEmitter } from 'events'
 
declare class MyEmitter extends EventEmitter {
  on(event: 'data', listener: (chunk: Buffer) => void): this
}

Verification

The TypeScript 3.8 type-only import notes (opens in new tab) and the consistent-type-imports rule (opens in new tab) are the fastest way to verify both compiler and linter behavior agree on the same import boundary.

  1. Run pnpm exec tsc --noEmit and confirm zero errors. If using verbatimModuleSyntax: true, any type-only import without import type will surface as a compiler error.
  2. Run the ESLint consistent-type-imports rule across the src/ directory and confirm zero violations (the rule is auto-fixable with --fix).
  3. Inspect the compiled JavaScript output for a module that uses only type imports — confirm the imported module paths do not appear in the output file.
  4. Search for import { in TypeScript files and verify each imported identifier is actually used as a runtime value, not only in type positions.

Use with AI

Copy these prompts to use with your AI assistant, or install the MCP server to use directly from Claude, Cursor, or Windsurf.

Check

Verify implementation

Review the import statements in this TypeScript file and identify any imports where only types or interfaces are used. These should use import type instead of a regular import.

Fix

Auto-fix issues

Convert the identified regular imports to import type where the imported binding is only used as a TypeScript type annotation and not as a runtime value.

Explain

Learn more

Explain the difference between import and import type in TypeScript, why type-only imports should be declared explicitly, and how verbatimModuleSyntax enforces this at the compiler level.

Review

Code review

Inspect all import statements in this file. For each import, check whether the imported name appears only in type positions (annotations, generics, implements clauses) versus runtime positions (function calls, new, typeof). Flag any regular import that is used only as a type.

Sources

References used to support the guidance in this rule.

Rules that often go hand-in-hand with this one.

Enable TypeScript strict mode in tsconfig.json

Enable "strict": true in tsconfig.json to activate the full suite of TypeScript type-checking flags and catch the most common runtime bugs at compile time.

JavaScript
Use ES modules (import/export)

Use native ES module syntax for imports and exports instead of CommonJS require() to enable static analysis, tree-shaking, and better tooling support.

JavaScript
Split large JavaScript bundles

Use dynamic imports and route-based code splitting to break large bundles into smaller chunks that load on demand, reducing initial page load time.

JavaScript
Validate external data at runtime with a schema library

Use Zod or Valibot to validate data from API responses, form inputs, localStorage, and environment variables — TypeScript types are erased at runtime and cannot protect against unexpected shapes.

JavaScript

Was this rule helpful?

Your feedback helps improve rule quality. This stays internal for now.

Loading feedback...
0 / 385