JavaScript to TypeScript Migration Checklist: A Safe Plan for Real-World Codebases
migrationjavascriptlegacy codestrict modechecklistcodebase modernization

JavaScript to TypeScript Migration Checklist: A Safe Plan for Real-World Codebases

TTypeScript Website Editorial Team
2026-11-03
7 min read

Use this phased JavaScript to TypeScript migration checklist to convert safely, adopt strictness, fix type gaps, and enforce progress in CI.

A JavaScript to TypeScript migration is safest when treated as a sequence of small, verifiable changes rather than a single rewrite. This checklist covers inventory, compiler configuration, file conversion order, strictness, third-party packages, tests, CI, and team conventions so you can track progress without stopping product development.

Overview

The goal of a migration is not to add annotations to every file as quickly as possible. It is to make important assumptions visible, catch defects earlier, and establish a maintainable TypeScript workflow. A large codebase may need to run JavaScript and TypeScript side by side for an extended period. That is a normal migration state, not a failure.

Before changing files, define what success means. Useful milestones include: TypeScript can check a representative part of the application; new code follows the agreed conventions; critical modules have explicit types; tests still pass; and CI prevents new type errors from entering converted areas. Record these milestones in the issue tracker rather than relying on an informal sense of progress.

Start with a baseline. Capture the current test status, build commands, lint results, known runtime errors, and the number of JavaScript files by area. This gives the team a way to distinguish migration problems from pre-existing problems. It also helps identify high-risk boundaries such as API clients, configuration loading, authentication, database access, and shared utility modules.

For compiler decisions, see the practical guide to TypeScript tsconfig options. For a broader project layout, the scalable TypeScript project structure guide can help you decide where converted files and shared types should live.

Checklist by scenario

For any JavaScript codebase

  1. Inventory the repository. List application entry points, packages, generated files, scripts, tests, build outputs, configuration files, and directories that should not be converted. Identify whether the project uses CommonJS, ESM, or a mixture.
  2. Choose a supported baseline. Confirm the runtime, package manager, test runner, bundler, deployment process, and editor integrations. TypeScript should fit the existing delivery path before the team changes that path.
  3. Add a separate TypeScript configuration. Define included source directories, excluded generated or dependency directories, output behavior, module settings, and the intended no-emit or build checks. Keep the configuration understandable and document unusual options.
  4. Enable JavaScript checking gradually. allowJs permits JavaScript files to participate in a TypeScript project, while checkJs asks TypeScript to report type errors in those files. Use them first on a manageable boundary, or enable checking through targeted files and comments when a whole-repository switch would create too much noise.
  5. Convert low-risk shared modules first. Begin with small utilities, constants, data models, and pure functions. Then move toward modules with side effects and external boundaries. This order exposes reusable typing patterns before the highest-risk code is changed.
  6. Rename deliberately. Convert implementation files to .ts and UI files containing JSX to .tsx. Update imports only as required by the project’s module rules, and verify that case-sensitive paths work in every supported environment.
  7. Run tests after each coherent group. A small commit that changes one module or feature is easier to review and revert than a repository-wide automated conversion.

For a small application

A small application can often migrate in one focused cycle. Establish tsconfig.json, convert the entry point and core modules, then move through features in dependency order. Enable strict checking once the first group is stable rather than leaving it as an indefinite final task. Keep the pull requests narrow enough that a reviewer can understand every type assertion and inferred type that matters.

For a frontend project, including React or Next.js

Convert shared domain types, API response handling, and state models before individual components. Components often expose missing assumptions about optional props, event values, loading states, and error states. Type those boundaries instead of silencing them with broad assertions.

Check the framework’s routing, server and client boundaries, environment variables, static assets, and test setup. In a Next.js project, keep data-fetching types close to the boundary where data enters the application, then validate untrusted responses at runtime. The TypeScript with Next.js guide provides a useful companion for these concerns.

For a Node.js backend

Prioritize request handlers, authentication, configuration, database adapters, queues, and external API clients. These modules translate untyped input into business decisions, so a type annotation alone is not enough. Use runtime schemas for request bodies, environment variables, and third-party responses where the application cannot control the input. See the guides to runtime API validation with Zod and type-safe environment variables.

If a dependency has incomplete types, isolate the gap behind a small adapter. Add a declaration file only when the shape is understood and maintained by the team. Avoid declaring an entire package as any if only one function is missing a type.

What to double-check

  • Compiler scope: Confirm that the files you think are being checked are actually included, and that generated output, dependencies, and temporary directories are excluded.
  • Strictness: Decide which strict options are active now and which have a scheduled milestone. Pay particular attention to nullable values, implicit any, unchecked indexed access, and function parameter variance.
  • Module behavior: Verify import and export behavior in development, tests, production builds, and the deployed runtime. A successful type check does not prove that module resolution is correct.
  • Runtime boundaries: Remember that TypeScript types are removed during compilation. JSON, forms, environment variables, HTTP requests, database rows, and user input still need runtime validation when their shape matters.
  • Third-party declarations: Check whether package types match the version actually installed. Prefer a typed wrapper or a focused declaration over repeated assertions throughout the codebase.
  • Tests and mocks: Update fixtures, factories, mocks, and test helpers. A production type may be correct while a test quietly supplies an impossible object.
  • Build ownership: Decide whether tsc performs type checking only or also emits JavaScript. Bundlers and tools such as esbuild, swc, tsup, or Vite may transform code without reporting every type error, so keep a dedicated type-check command where appropriate. The build tools comparison can help clarify these roles.
  • Aliases and paths: If you introduce or preserve path aliases, test them in the editor, test runner, bundler, and production runtime. TypeScript’s understanding of an alias does not automatically configure every other tool; consult the path alias guide.

Use a migration scorecard with columns for area, owner, current status, remaining errors, runtime risk, test coverage, and next action. A count of converted files is useful, but it should not be the only measure. A smaller number of well-typed boundaries can deliver more value than a large number of files containing any and unchecked assertions.

Common mistakes

Converting everything in one branch. This creates difficult reviews, merge conflicts, and unclear regressions. Prefer incremental changes that preserve a working build.

Using any as the migration strategy. Temporary escape hatches can be practical, but label them, keep them local, and create follow-up work. Consider unknown at untrusted boundaries so the next operation must narrow the value.

Turning on every strict option without preparation. Strictness is a worthwhile destination, but a sudden repository-wide error flood can hide important failures. Establish a baseline, fix errors by category, and increase enforcement in stages.

Trusting declarations as runtime validation. An interface does not inspect incoming JSON. Validate external data before using it, especially for authentication claims, configuration, payments, permissions, and persistence.

Ignoring generated files and declarations. Generated code should usually be excluded and regenerated through a documented command. Handwritten declaration files should have an owner and tests or usage checks that reveal when the underlying library changes.

Changing tooling and language at the same time. Replacing the bundler, test runner, module system, and language in one migration makes failures hard to attribute. Keep the first TypeScript phase as close as possible to the existing workflow.

When to revisit

Revisit this checklist before a seasonal planning cycle, after a major dependency or runtime change, and whenever the build, test, or deployment workflow changes. It is also worth reviewing after a team grows, a new package is added, or a previously internal module becomes a public API.

At each review, check whether JavaScript-only areas are still receiving new code, whether temporary declarations or any usages have owners, and whether CI enforces the intended boundary. Reassess strictness milestones rather than allowing the migration to remain permanently half-finished.

For a practical next step, choose one vertical slice: inventory its dependencies, enable checking, convert the smallest useful modules, update tests, and add a CI check for that slice. Record what caused friction and turn the result into a team convention. Repeat the process until the remaining JavaScript is either intentionally retained, generated, or ready for a clearly owned conversion.

Related Topics

#migration#javascript#legacy code#strict mode#checklist#codebase modernization
T

TypeScript Website Editorial Team

Senior SEO Editor

Senior editor and content strategist. Writing about technology, design, and the future of digital media. Follow along for deep dives into the industry's moving parts.