Migrating JavaScript to TypeScript: The Guide for Teams (strict mode included)

The incremental path from JavaScript to TypeScript: a six-stage plan from allowJs to the CI gate, the right order for the strict flags, honest effort figures from the Airbnb, Stripe, and Pinterest migrations — and the cases where you are better off not migrating.
8 min readMatthias RadscheitMatthias Radscheit
Happycodingen-US

TL;DR

Migrate JavaScript codebases incrementally: enable allowJs, write new files in TypeScript only, convert legacy code as you touch it, stage the strict flags, finish with a CI gate. Airbnb, Stripe, and Pinterest show codemods are massive accelerators; realistic timelines run months to years. Untyped code is also an AI governance risk — 94 percent of compile errors in AI code are type errors (GitHub Octoverse 2025).

  • The official migration path is incremental: enable allowJs, rename files one at a time, raise strictness step by step — documented in the TypeScript Handbook.
  • The order of the strict flags decides team buy-in: noImplicitAny first, then strictNullChecks, the full strict bundle last.
  • Codemods are the proven accelerator: Airbnb converted over 50,000 lines per day with ts-migrate (2020); Pinterest handled 3.7 million lines in eight months (2025).
  • Calendar time is not team time: with the touch-it-type-it principle, the migration runs alongside regular work over months to years — with no dedicated migration budget.
  • An untyped codebase is an AI governance risk: 94 percent of compile errors in AI-generated code are type errors (GitHub Octoverse 2025).
  • Do not migrate if the codebase is small and stable, its remaining lifespan is short, or it is pure configuration scripting — the effort yields no return there.

94 percent of compile errors in AI-generated code are type errors — that is the finding from GitHub Octoverse 2025. If you let AI assistants loose on an untyped JavaScript codebase, you have disabled exactly the verification layer that catches the most common failure mode of machine-generated code before merge. That makes the TypeScript migration no longer a style debate inside the engineering department, but part of your AI governance: the type system checks every AI suggestion automatically — without a human spending a single minute of review time on it.

The market has already answered the question of whether. According to State of JS 2025, 40 percent of respondents write TypeScript exclusively, and only 6 percent still write plain JavaScript. This guide therefore covers the how — the sequence, the real-world effort, the places where migrations actually fail. And, because honesty is cheaper here than later: the cases where you are better off not migrating at all.

Why now: An untyped codebase is a double risk

The first risk is the classic one. In an untyped codebase, the knowledge of which function expects which data shape lives in the heads of the developers who wrote it. Every backfill hire, every refactoring, every integration turns into archaeology. TypeScript moves that knowledge out of heads and into the compiler — that is the real maintainability gain, not the autocomplete in the editor.

The second risk is new. AI assistants dramatically increase the volume of change in your codebase while human review capacity stays constant. The only control layer that scales with that volume is a machine-driven one — and the 94 percent figure from GitHub Octoverse 2025 shows it works precisely on the dominant error type. That the talent and cost side is consolidating toward TypeScript anyway is something we worked through in our TCO comparison of TypeScript vs. Java; for JavaScript codebases, the logic applies all the more, because the switch does not change the language — it only adds a type system.

Big bang or incremental?

The official migration path is incremental and documented in the TypeScript Handbook: enable allowJs in the compiler configuration, rename files from .js to .ts one at a time, raise strictness step by step. JavaScript and TypeScript coexist in the same project for as long as you like — there is no deadline by which everything has to be done. Big-bang migrations demonstrably work too, but only under conditions the midmarket rarely has: Pinterest migrated roughly 3.7 million lines via codemod in a single pass in 2025 — safeguarded by a byte-for-byte comparison of the transpiled output; Stripe needed months of codemod preparation in 2022 before the one big pull request for a similar scale could be merged. If you do not have a dedicated migration team with that depth of tooling, go incremental. That is not a fallback of convenience — it is the path the vendor designed.

The staged plan: Six stages from allowJs to the CI gate

The following sequence has proven itself in our projects and matches the official path. What matters is that every stage has a measurable finish line — otherwise the migration becomes a permanent condition nobody is steering anymore.

StageActionComplete when
1 — Coexistence setupIntegrate the TypeScript compiler into the build, enable allowJs; not a single file is renamedThe build stays green unchanged and runtime behavior is identical
2 — New files in TypeScript onlyTeam rule: every new file is created as a TypeScript file; code review enforces itNo new JavaScript files appear in the repository
3 — Legacy code as you touch itWhoever changes a file for business reasons renames and types it in the same passThe migration ratio climbs monthly alongside normal feature work — with no dedicated budget
4 — Type boundaries to legacy modulesDefine declaration files for not-yet-migrated modules and untyped dependenciesMigrated files no longer import anything unchecked as any
5 — Turn on strict flags in stagesnoImplicitAny first, then strictNullChecks, the full strict bundle lastAll flags active, the error list is empty, remaining exceptions are documented
6 — CI gateType checking blocks merges; the migration ratio is reported as a KPIRegressions are technically impossible; progress is part of management reporting

The touch-it-type-it principle in stage 3 is the economic core of the plan: it couples migration work to code that is being touched anyway — meaning places where the team currently has the context in their heads and the typing effort is lowest. Rarely touched, stable modules are deliberately left for last. That is not a blemish, it is prioritization: types pay off most where code changes.

Stage 4 is the most inconspicuous and the one most often skipped — with consequences. As long as migrated files accept their imports from legacy modules unchecked as any, type safety at exactly those boundaries is a promise without backing: the compiler reports green but checks nothing there. Declaration files for the most important legacy modules and untyped dependencies close that gap without touching the legacy code itself — you describe only the public interfaces, not the implementation. As a rule of thumb, we prioritize the modules imported by the largest number of already-migrated files: that is where each line of declaration buys the most safety.

The strict flags in the right order

The most common mistake in stage 5 is activating the full strict bundle immediately. That produces thousands of errors in one blow, the team gets used to red builds, and the migration loses its authority. The order recommended in the Handbook starts with noImplicitAny — the flag with the best ratio of effort to safety gained, because it surfaces silently untyped code. Next comes strictNullChecks, which in our experience finds the most real bugs but also creates the most work. Only at the end comes the complete strict bundle — the umbrella option that activates all strict checks at once. If you enable checkJs in parallel, you get type checking even in JavaScript files that have not been renamed yet — useful as an early-warning system, but optional.

Stage 6 makes the migration fully steerable: type checking blocks merges, and the migration ratio — the share of typed files or lines — moves into regular reporting as a KPI. Progress then no longer depends on memory and goodwill but is measurable like any other technical metric. For you as a decision-maker, this is the point where a developer initiative becomes a program with a KPI, an owner, and a target date — even if that target date deliberately sits far out.

Honest effort: Calendar time is not team time

The most reliable public numbers come from three engineering blogs. Airbnb built the codemod tool ts-migrate in 2020 (now open source) and used it to convert projects with over 50,000 lines and more than 1,000 files in a single day — and was still only at 86 percent of its 6-million-line monorepo at the time of the report, because the migration ran alongside regular work. Stripe converted its largest frontend, 3.7 million lines, in a single pull request in 2022 — after months of codemod preparation. Pinterest handled the same order of magnitude in eight months in 2025, incident-free. So the honest range is: months with a focused, tool-supported approach — years if the migration runs alongside feature work.

Two qualifications belong here. First: Stripe and Pinterest migrated from Flow, an already typed system — their starting point was more favorable than raw JavaScript, where types still have to be worked out. Second: your codebase probably does not have millions of lines. For the midmarket, the relevant metric is therefore not calendar time but team time: with the touch-it-type-it principle, the migration does not tie up a dedicated developer — it spreads as a small surcharge across work that happens anyway. Calendar time gets longer as a result — that is the deliberately chosen price. What you need in return is patience in your reporting: a ratio that climbs from 20 to 80 percent is a success, even if it takes four quarters to get there.

When you should NOT migrate

A guide that only knows the case in favor is sales prose. There are constellations in which the migration does not pay off economically — and a clean no saves more money there than a half-hearted yes. The four most common:

Small, stable codebase: If an application has been running for years, sees hardly any changes, and its error rate is unremarkable, typing buys you almost nothing. Types pay off when code changes — where nothing changes, safeguarding through tests and monitoring is enough.

Short remaining lifespan: If replacing the application is already on the roadmap, every euro belongs in the successor, not in the legacy system. A migration whose result gets shut down before it amortizes is make-work.

Pure configuration and glue scripting: Build scripts, deployment automation, small internal tools — JavaScript is fine here. The governance case from the introduction concerns product code that AI assistants and changing teams will evolve for years.

When the real risk lies elsewhere: If a PHP backend on a version without security patches sits underneath your JavaScript frontend, typing is not your most urgent problem. We have a dedicated guide for that case: Migrating legacy PHP to a TypeScript stack.

The next step

The entry point is deliberately small: stage 1 — the coexistence setup with allowJs — is done in a few days and changes nothing about runtime behavior. After that, it is not technology that decides but discipline: the review rule for new files, the touch-it-type-it principle, the staged strict flags, the CI gate. As a TypeScript agency, we take on exactly that part — from setup through the strict staging to team onboarding, typically in a retainer model for €4,000–12,000 per month, running alongside your feature work.

If you first want to know where your codebase stands: the starting point is our modernization assessment at a fixed price after discovery — inventory, risk picture, staged plan with effort estimate. Details on our software modernization page, or clarify your starting position directly in a free 30-minute call.

As of July 2026. All prices net of VAT. Market figures cited with sources in the text; project prices are published happycoding anchors from real projects.

Frequently asked questions

How long does a migration from JavaScript to TypeScript take?
The documented range runs from months to years. Pinterest migrated 3.7 million lines in a focused eight months (2025); Airbnb was at 86 percent of its monorepo after years of migrating alongside regular work (2020). For midmarket codebases: with tooling and focus, months are realistic; with the touch-it-type-it principle running parallel to feature work, it takes longer but ties up almost no dedicated capacity.
Do we have to stop feature development for the migration?
No. The incremental path is built for exactly this: with allowJs, JavaScript and TypeScript coexist in the same project for as long as needed. Under the touch-it-type-it principle, the team only types files it is changing for business reasons anyway. The migration runs as a small surcharge on normal work — the price is longer calendar time, not blocked roadmap capacity.
Should we enable strict mode from the start?
No. In a legacy migration, the full strict bundle immediately produces thousands of errors, and the team gets used to red builds. The TypeScript Handbook recommends raising strictness step by step — noImplicitAny first, because it surfaces untyped code; in practice, strictNullChecks follows before the complete bundle is switched on. Each stage is completed before the next one begins.
What does allowJs actually do?
The compiler flag allowJs lets the TypeScript compiler accept existing JavaScript files as input. That makes it possible to integrate TypeScript into the build without renaming a single file — the starting point of every incremental migration. As a complement, checkJs optionally type-checks JavaScript files that have not been renamed yet, serving as an early-warning system for the legacy code.
Is a big-bang approach ever the right call?
Only with substantial tooling maturity. Stripe converted 3.7 million lines in a single pull request — after months of codemod preparation. Pinterest safeguarded its big bang with a byte-for-byte comparison of the transpiled output. Both also started from Flow, an already typed system. Without a dedicated migration team and those safeguards, the incremental path is the right choice.
What does the migration cost with happycoding?
The starting point is a modernization assessment at a fixed price after discovery: inventory, risk picture, and a staged plan with an effort estimate. Guiding the migration itself — setup, strict staging, review rules, team onboarding — typically runs in a retainer model for €4,000 to €12,000 per month, parallel to your ongoing feature development. All prices net of VAT.

Sources

Related articles

Open for select projects

Let's talk about your project

Book a no-obligation call, send us an email, or use the form – we'd love to hear from you.

150+
Completed projects
15
Years of experience
8
Senior‑level team members