Migration to TypeScript

Learn a practical, low-risk strategy to migrate an existing JavaScript codebase to TypeScript. This guide covers incremental adoption, strictness control, file-by-file conversion, and production-safe rollout.

On this page

Why migration strategy matters

Most teams adopt TypeScript after a JavaScript codebase already exists. A successful migration is not about converting everything at once. It is about reducing risk, keeping delivery speed, and improving safety incrementally. Done right, TypeScript migration improves reliability without stopping feature work.

Start with boundaries, not the whole codebase

The highest leverage migration targets are boundaries: API clients, data models, shared utilities, and service layers. If these are typed, much of the codebase benefits immediately even if many files remain JavaScript.

Enable TypeScript incrementally

Create a tsconfig.json and start compiling TypeScript alongside existing JavaScript. Many projects begin with allowJs enabled.

{
  "compilerOptions": {
    "allowJs": true,
    "checkJs": false,
    "strict": true
  }
}

allowJs lets you include .js files in the project. checkJs can be enabled later to type-check JavaScript gradually.

Adopt file-by-file conversion

Convert one module at a time. Start with small, stable modules: helpers, constants, formatting utilities, and pure functions. Each converted file increases safety and reduces future migration cost.

Prefer typing at the edges

For maximum impact, type data that crosses boundaries: HTTP payloads, storage objects, config, and public component props. Internals can rely on inference once boundaries are typed.

Replace any with unknown and narrow

During migration, it is tempting to use any to silence errors. Instead, use unknown and narrow safely. This preserves safety while still allowing progress.

function parseJson(value: string): unknown {
  return JSON.parse(value);
}

Use type assertions only as temporary bridges

Type assertions can unblock migration, but treat them as debt. Keep assertions isolated and replace them with proper types or validation as you touch the code.

Introduce linting and CI checks

Migration becomes easier when type errors are caught automatically. Add TypeScript compilation to CI and fail builds on type errors for converted modules. This prevents regressions and gradually raises quality.

Handle third-party typing gaps

During migration, missing types from external libraries are common. Prefer installing @types packages, writing minimal .d.ts files, or wrapping libraries behind typed adapters. Avoid polluting the codebase with any.

Strict mode: keep it on

New TypeScript files should be written in strict mode. If legacy code makes strict adoption difficult, do not disable strict globally. Instead, migrate incrementally and use well-scoped solutions such as improving types at boundaries.

Common migration phases

  • Phase 1: Add TypeScript toolchain, compile TS alongside JS.
  • Phase 2: Type shared models and API boundaries.
  • Phase 3: Convert utilities and stable modules.
  • Phase 4: Convert high-risk modules (forms, parsing, business logic).
  • Phase 5: Reduce remaining any and strengthen validation.

Common mistakes

  • Trying to convert the whole codebase at once.
  • Using any everywhere to make errors disappear.
  • Disabling strict mode globally.
  • Not enforcing type checks in CI, allowing regressions.

Production guidance

  • Migrate incrementally with clear milestones.
  • Type boundaries first for maximum impact.
  • Use unknown and narrowing instead of any.
  • Keep strict mode enabled for new TS code.
  • Automate type checking in CI.

What’s next

Next, we will cover mixed JS projects: how to run JavaScript and TypeScript together safely, including allowJs, checkJs, JSDoc typing, and gradual enforcement.