Dashboard

Convert JavaScript to TypeScript With AI in Safe Batches

An AI assistant can rename every file to .ts in a minute and leave you with a project that type-checks and checks nothing. The fix is a ledger.

Carlo Zuercher
Carlo Zuercher
Staff Engineer, Platform
15 October 20261 min read

You can convert a JavaScript project to TypeScript with AI in small batches: turn on allowJs so both file types coexist, rename a handful of files at a time starting with the ones that import nothing else, have the assistant add real types, and track how many any escapes and @ts-ignore comments each batch adds. The ledger is the part people skip, and it is what separates a migration from a rename.

Why the one-shot conversion disappoints

Ask an assistant to "convert this repo to TypeScript" and it will happily comply. Files get a .ts extension, parameters get types, and the compiler goes quiet. Look closer and many of those types are any, a few errors are muted with // @ts-ignore, and some objects are cast with as any to get past a stubborn line.

The compiler is satisfied, so the result feels finished. But any turns checking off for that value. You now have the maintenance cost of TypeScript with a fraction of the protection, and nobody can tell which parts are real. The TypeScript handbook frames strictness as a dial you can turn up gradually, and that framing is the right one: TypeScript's guide to migrating from JavaScript. The goal of this plan is to keep the dial visible.

Step 1: Let both languages coexist

Put sources in a src folder and create a tsconfig.json. The handbook's starting point sets allowJs, which lets TypeScript accept JavaScript files as inputs, and that is what makes incremental migration possible.

json
{
  "compilerOptions": {
    "outDir": "./built",
    "allowJs": true,
    "noImplicitAny": true
  },
  "include": ["./src/**/*"]
}

The handbook suggests enabling noImplicitAny early, so TypeScript flags places where a type would silently become any instead of letting them slide. It also lists noEmitOnError as a way to stop output when there are type errors, if you want that guard. Run tsc once before touching any file, so you know what a clean baseline looks like.

Step 2: Start the ledger

Before the first rename, record three numbers. These commands work in a shell at the project root:

bash
grep -rEn ": any|as any|<any>" src | wc -l
grep -rEn "@ts-ignore|@ts-expect-error" src | wc -l
find src -name "*.js" | wc -l

Write them in a note: explicit any count, suppression count, JavaScript files remaining. After each batch, run the same three commands. The first two should stay flat or fall, and the third should fall. A batch that raises the first two did not finish its job.

Step 3: Convert leaves first

Order matters. Start with files that import nothing from your own code: constants, small utilities, formatting helpers. Their types are easy to get right, and every later file that imports them then has something real to check against.

Work inward from there. A batch of five to ten files is enough for a reviewer to read in one sitting. The handbook's own first step is the same shape: rename one .js file to .ts (or .tsx if it uses JSX), then deal with the errors it reveals, which it likens to red squiggles in a word processor.

Step 4: Give the assistant rules, not a request

Write the constraints into the prompt. Use this as a starting point and adjust it to your project:

text
Convert these files from JavaScript to TypeScript: [list of 5 to 10 files].

Rules:
- Do not use `any`, `as any` or `@ts-ignore`. If a type is unclear,
  use `unknown` and narrow it with a check, or stop and ask me.
- Convert `require` and `module.exports` to `import` and `export`.
- Define interfaces for objects that cross a file boundary.
- Do not change runtime behaviour. No refactors, no renames.
- After the change, list every place where you were unsure of a type.

Then show the output of `tsc` for just these files.

The last two lines are doing real work. A list of "places I was unsure" is the most useful thing an assistant can hand a reviewer, and compiler output beats a claim that it compiles.

The handbook also covers the mechanical fixes you will see in these diffs: replacing require with import, installing declaration packages such as @types/lodash, and turning module.exports into a top-level export.

Step 5: Review the diff for three things

  1. Ledger numbers. Did any or suppressions go up?

  2. Behaviour. A type-only conversion should not change logic. If a function body changed, ask why.

  3. Shape of the interfaces. Types the assistant invented should match the data your code actually receives. An interface that describes what the code wishes it got is worse than none.

Then run your existing tests. If you have none, that is the better problem to solve first, and the approach in moving a large code migration to an AI coding agent shows how to build a safety net before the agent touches anything.

Step 6: Turn the dial

When the last .js file is gone, tighten checks one at a time. The handbook names strictNullChecks, which gives null and undefined their own types, and noImplicitThis, which flags untyped this. Turn one on, fix what it reveals, commit, then try the next. This is where the real bugs show up, such as the function that returns undefined on one path nobody remembered.

If a single flag unleashes hundreds of errors, fixing TypeScript errors with AI covers how to work through a big list without muting it. For a project that is old as well as untyped, migrating a legacy codebase with AI is the bigger picture, and the wider tool landscape is in AI coding tools.

Questions people ask

Can AI convert a whole JavaScript project to TypeScript?

It can rename and annotate the whole project quickly, but the quality of the types depends on the rules you set. Without a ban on any, expect many escapes that the compiler accepts and a reviewer should not.

Should I convert everything at once or file by file?

File by file, in small batches. With allowJs enabled, JavaScript and TypeScript files work together, so the project keeps running throughout.

Which files should I convert first?

Files with no imports from your own code. Their types are simplest, and every file that depends on them benefits straight away.

Is unknown better than any?

Usually, yes. unknown forces you to check a value before using it, while any switches checking off. When an assistant is unsure of a type, unknown plus a narrowing check is the safer default.

How did this land?

About the author

Carlo Zuercher
Carlo Zuercher

Staff Engineer, Platform

Carlo works on the platform that turns prompts into running apps. He writes the engineering deep dives and the changelog notes worth reading.

Share

Get the next post in your inbox

One email a month. Product updates, engineering posts, and the best of Built with Swarmz.

I agree to receive emails about AI building tips and Swarmz product news. Unsubscribe any time.