Skip to content
CodeMorph

Download CodeMorph (Beta)

Leave your email, then pick your computer. We'll only write about the beta and new releases.

Guide

How to migrate JavaScript to TypeScript (2026 guide)

Add a tsconfig, measure your type errors, convert files from the bottom of your import graph upward, and type-check after every batch. Below are the real steps with code, and an honest look at the tools that can do parts of the work for you: ts-migrate, AI coding agents and CodeMorph.

Download CodeMorph (Beta)

Leave your email, then pick your computer. We'll only write about the beta and new releases.

Compare the tools

Windows 10/11 · Free during beta · Bring your own Claude key or use Ollama

Windows may show 'unknown publisher'. Click More info, then Run anyway.

The short answer

  1. Start from a clean git state with passing tests, and add TypeScript with a permissive tsconfig that allows JavaScript.
  2. Measure your baseline: how many type errors your JavaScript already has.
  3. Type your dependencies first, then convert files leaf-first: files that import nothing local go before the files that use them.
  4. Type-check the whole project after every batch, and fix errors with real types instead of any.
  5. Raise strictness in steps, and run tsc in CI so the codebase never slides back.

The steps

A JavaScript to TypeScript migration, step by step

These steps work with any tool, or with none. They are the same ones the tools below automate in different ways.

Step 1: Prepare the repo

Commit everything, and make sure your tests pass before you touch a file. If a test fails later, you want to know the migration caused it. Create a branch for the work so every step can be reviewed and undone.

Install TypeScript as a dev dependency, along with the type definitions for the Node version you target.

Terminal
npm install --save-dev typescript @types/node

Step 2: Add a permissive tsconfig.json

Start loose. The goal of this step is to let JavaScript and TypeScript files live side by side, so you can convert one file at a time and always have a working project. The allowJs option is what makes that possible.

tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "allowJs": true,
    "checkJs": false,
    "noEmit": true,
    "strict": false,
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "skipLibCheck": true
  },
  "include": ["src"]
}

Adjust module and moduleResolution to match how your project runs. For a bundler or Next.js project the values above work as written. For a plain Node project, use the Node settings from the TypeScript docs.

Step 3: Measure your baseline

Turn on checkJs once, run the compiler, and count the errors. Your JavaScript already has type errors, mostly places where the compiler can see a value is used inconsistently. That number is your baseline, and progress is measured against it, not against zero.

Terminal
npx tsc --noEmit --checkJs --maxNodeModuleJsDepth 0 | grep -c "error TS"

On an untyped codebase this is often in the hundreds or thousands. That is normal. Do not try to fix it all at once.

Step 4: Type your dependencies

Before you convert a file, make sure the packages it imports have types. Many popular packages ship their own. For the rest, install the matching @types package. For a package with no types at all, declare it once so imports stop failing.

Terminal
npm install --save-dev @types/express @types/lodash
src/types/untyped-modules.d.ts
declare module "some-untyped-package";

Step 5: Convert leaf files first

Types flow upward through imports. If you convert a file before the files it imports, you have to guess what those imports return. If you convert the imports first, the answer is already in a typed file. So start with the files that import nothing local, such as utilities and helpers, and work toward your entry point. Files that import each other in a cycle have to move together.

Here is a typical leaf file. Rename it from .js to .ts, switch to ES module syntax and add the types the code already implies.

src/lib/money.js (before)
function formatMoney(cents, currency = "USD") {
  return new Intl.NumberFormat("en-US", { style: "currency", currency }).format(cents / 100);
}

module.exports = { formatMoney };
src/lib/money.ts (after)
export function formatMoney(cents: number, currency: string = "USD"): string {
  return new Intl.NumberFormat("en-US", { style: "currency", currency }).format(cents / 100);
}
tsc
src/orders.js:14:21 - error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.

Now every file that imports formatMoney is checked against the real signature. If a caller passes a string where it should pass a number, the compiler tells you, and that is the kind of bug migrations exist to find.

Step 6: Fix errors without reaching for any

After each batch of files, run tsc across the whole project, not just the files you changed. A change to a typed module can break the files that import it. When an error appears, resist the quick escape of any. Use unknown for values you cannot trust yet, and narrow them.

any versus unknown
// Avoid: the compiler stops checking everything downstream
function getId(input: any) {
  return input.id;
}

// Prefer: say what you know, and check the rest
function getId(input: unknown): string {
  if (typeof input === "object" && input !== null && "id" in input && typeof input.id === "string") {
    return input.id;
  }
  throw new Error("Expected an object with a string id");
}

Step 7: Raise strictness in steps

Once every file is TypeScript, tighten the configuration one flag at a time instead of turning on strict all at once. Fix the errors each flag reveals, commit, then move on.

tsconfig.json
{
  "compilerOptions": {
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true
    // when all of these are clean, replace them with:
    // "strict": true
  }
}

Step 8: Keep it from sliding back

Add the type-check to CI so a pull request with type errors cannot merge. This is the step that makes the migration permanent.

.github/workflows/typecheck.yml
name: typecheck
on: [pull_request]
jobs:
  tsc:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - run: npm ci
      - run: npx tsc --noEmit

The tools

Four ways to do the conversion

You can run all of the steps above by hand. These are the ways people automate the conversion, with what each one does well and where it stops.

By hand, with the compiler

What it does well
You decide every type. The result is as good as your care, and you learn the codebase. The editor's built-in quick fixes, such as inferring parameter types from usage, speed it up.
Where it falls short
Slow on a large codebase, and easy to stall halfway. Nothing tells you what order to work in.
Best when
Small projects, or a few modules you want to get exactly right.

ts-migrate

What it does well
A free, open-source command-line tool from Airbnb. Its README says it takes a JavaScript, or partial TypeScript, project in and gives a compiling TypeScript project out. No AI model, works offline, and scriptable.
Where it falls short
By its own README, "there will be lots of // @ts-expect-error, and any that will need to be fixed over time." The output builds, but a follow-up is required to improve type safety.
Best when
A very large codebase where getting everything compiling quickly matters more than the quality of the first set of types.
Terminal
npm install --save-dev ts-migrate
npx -p ts-migrate -c "ts-migrate-full src"

AI coding agents and assistants

What it does well
They understand intent, so they can write real types and meaningful names, and an agent can run tsc and iterate on the errors. Good for converting one file or one module quickly.
Where it falls short
They work with whatever context you give them. A model that cannot see an imported file may invent its shape. Ordering, whole-project verification and a way back are up to you.
Best when
Small projects, single modules, or when you want to talk through a tricky file.

CodeMorph

What it does well
A desktop code editor built for this one job. It maps your dependency graph and plans leaf-first batches, converts each batch against imports that are already typed, type-checks the whole project after every batch and sends compiler errors back for repair. You review each file as a diff, and a checkpoint is saved first, so one click on Undo all restores your project byte for byte.
Where it falls short
It is in beta, for Windows and Mac. It needs an AI provider key (or local models with Ollama), and it is an editor, not a command you can run in CI. Running your tests and build from inside CodeMorph is coming soon.
Best when
A real codebase where you want an AI migration that is ordered, checked and reversible.

CodeMorph vs ts-migrateBest tools to migrate JavaScript to TypeScript

Choosing

Which approach should you use?

Your situationWhat fits
A handful of files, or a project you know wellDo it by hand with the compiler, and use an AI assistant for tricky files.
Hundreds or thousands of files, and you mainly need it to compile soonts-migrate, then pay down the any and @ts-expect-error comments over time.
A mid-size or large codebase, and you want real types with every step checkedCodeMorph, or an AI agent you drive carefully with tsc after each batch.
You cannot send code to any AI providerBy hand or ts-migrate, or CodeMorph with a local model through Ollama.

Questions about migrating JavaScript to TypeScript

What is the easiest way to migrate JavaScript to TypeScript?
Add a tsconfig.json with allowJs, rename files from .js to .ts one at a time starting with the files that import nothing local, and run tsc after each batch. Tools such as ts-migrate, AI coding agents and CodeMorph automate parts of this.
What tool converts JavaScript to TypeScript automatically?
Several can. ts-migrate and TypeStat are open-source command-line tools. AI coding agents can convert files you point them at. CodeMorph is an editor that plans an AI migration in dependency order and type-checks every batch. They differ in how good the resulting types are and in how much checking you have to do yourself.
Can I migrate gradually, without converting everything at once?
Yes. With allowJs enabled, JavaScript and TypeScript files can sit side by side, so you can ship between batches.
Should I turn on strict mode from the start?
Usually not. Get every file to TypeScript with a loose configuration, then raise strictness one flag at a time so each wave of errors stays manageable.
Will migrating change how my code behaves?
Types are erased at build time, so they do not change behavior. Other parts of a migration can, such as switching from CommonJS to ES modules, so run your tests after each batch.
Can ChatGPT or Claude convert my JavaScript to TypeScript?
They can convert a file well when you give them the right context. Across a whole project the risks are missing context for imports, no fixed order, and no automatic check, so run the compiler over the whole project after each batch.
Is ts-migrate still a good choice?
It is a good fit when you want a free, scriptable tool that gets a large codebase compiling quickly, as long as you plan the follow-up work on the any types and @ts-expect-error comments it leaves. Check its repository for current activity before you commit to it.
Does CodeMorph cost anything?
CodeMorph is free during the beta. You use your own AI provider key, or local models through Ollama.

Migrate JavaScript to TypeScript with CodeMorph.

Download the beta, open a project and see your migration plan in under a minute.

Download CodeMorph (Beta)

Leave your email, then pick your computer. We'll only write about the beta and new releases.

Read the docs

Windows 10/11 · Free during beta · Bring your own Claude key or use Ollama

Windows may show 'unknown publisher'. Click More info, then Run anyway.