- 3.7 million lines converted in a single pull request on Sunday March 6, 2022
- Months of codemod development; one day of execution
- AST-based codemod built internally using jscodeshift — no manual migration
-
tsc --noEmitas the correctness oracle: if it compiles, the transformation is correct - Zero Flow type errors after migration — 100% TypeScript compilation success
- Atomic migration — single PR, no mixed state, hundreds of engineers writing TypeScript on Monday
On Sunday, March 6, 2022, Stripe merged a single pull request that converted their entire largest JavaScript codebase from Flow to TypeScript. 3.7 million lines of code. Hundreds of engineers arrived Monday morning to start writing TypeScript. The migration had been invisible until it wasn't.
The Story
In March 2022, Stripe's engineering blog announced something that stopped engineers mid-scroll: On Sunday, March 6, we migrated Stripe's largest JavaScript codebase from Flow to TypeScript. In a single pull request, we converted more than 3.7 million lines of code. The next day, hundreds of engineers came in to write TypeScript for the first time. Understanding how a 3.7-million-line migration becomes a single pull request requires understanding the architectural approach: you don't migrate 3.7 million lines manually, you build a machine that migrates them for you.
The decision to migrate from Flow (Facebook's open-source JavaScript type checker with its own annotation syntax) to TypeScript (Microsoft's statically typed superset of JavaScript, which became the de-facto standard for typed JavaScript after 2018) was driven by practical engineering considerations. Flow's tooling had fallen behind TypeScript's in IDE integration quality — autocomplete, inline error reporting, and refactoring support were all noticeably worse in editors like VS Code. The ecosystem had moved: most open-source libraries shipped TypeScript type definitions, not Flow definitions, forcing Stripe's engineers to write manual stubs or use untyped imports. The talent pipeline had shifted: engineers coming from university and other companies expected TypeScript. Every month on Flow was a month accumulating migration debt that would only grow harder to pay.
Problem
3.7 Million Lines on a Declining Type System
Stripe's largest JavaScript codebase was typed with Flow at a time when Flow was a competitive choice. By 2022, TypeScript dominated the industry: better IDE support, a larger ecosystem, and a growing talent pool that expected TypeScript. Every month on Flow was accumulating migration debt while engineering productivity fell behind TypeScript-typed equivalents.
Cause
Migration Scale Made Manual Approach Infeasible
3.7 million lines across hundreds of files cannot be migrated manually without years of effort and severe consistency problems. The type annotation syntax differences between Flow and TypeScript are pervasive — every file would need to be touched. An automated approach was required, which meant building a production-quality migration tool before the migration could begin.
Solution
AST-Based Codemod: Build the Machine
Stripe's engineering team built a codemod that parsed Flow-annotated TypeScript files using an AST (Abstract Syntax Tree — a tree representation of code's structure that enables programmatic analysis and transformation without working with raw text strings) parser, applied transformation rules for each Flow-to-TypeScript annotation conversion, and emitted valid TypeScript. The tool was built and iterated on for months before migration day.
Result
One Pull Request, One Sunday, Done
On March 6, 2022, Stripe merged a single PR converting 3.7 million lines. Monday, hundreds of engineers arrived to find their codebase in TypeScript. The migration was complete, clean, and consistent — because a machine did it, not hundreds of engineers doing it differently.
On Sunday, March 6, we migrated Stripe's largest JavaScript codebase from Flow to TypeScript. In a single pull request, we converted more than 3.7 million lines of code. The next day, hundreds of engineers came in to start writing TypeScript for their projects.
— Stripe Engineering, via Stripe Engineering Blog
The Fix
The Codemod: Engineering the Migration Machine
The codemod is itself a significant engineering artifact. It had to handle every type annotation pattern present in 3.7 million lines of production code — including patterns that were technically valid Flow but unusual, patterns generated by code generation tools, and patterns accumulated across years of different engineers with different Flow styles. The tool used jscodeshift (an open-source JavaScript codemod toolkit from Facebook that parses code into an AST, applies transformation functions, and prints the modified AST back to source code while preserving formatting) as its transformation framework, with custom rules for each Flow-to-TypeScript conversion.
- 3.7M — lines of code converted in the migration — the largest single JavaScript codebase at Stripe
- 1 PR — deployment vehicle for the entire migration; atomic, consistent state across all 3.7 million lines simultaneously
- 1 day — execution time; months of codemod development compressed into a single Sunday deployment
- 100% — TypeScript compilation success rate after migration — the correctness oracle that validated the codemod was production-ready
// Simplified example of a Flow-to-TypeScript codemod transformation rule
// Real implementation uses jscodeshift AST transformation framework
// FLOW syntax:
// type Props = { name: string, age: number } // commas in object types
// const foo = (x: ?string) => x // ?string = nullable in Flow
// import type { User } from './types' // type import
// TYPESCRIPT equivalents:
// type Props = { name: string; age: number } // semicolons, not commas
// const foo = (x: string | null) => x // explicit union, not ?
// import type { User } from './types' // same syntax — lucky!
module.exports = function transformer(file, api) {
const j = api.jscodeshift;
const root = j(file.source);
// Transformation rule: Flow nullable ?T → TypeScript T | null
root.find(j.NullableTypeAnnotation).replaceWith(path => {
return j.unionTypeAnnotation([
path.node.typeAnnotation, // the original T
j.nullLiteralTypeAnnotation(), // | null
]);
});
// Transformation rule: Flow object types use commas; TS uses semicolons
// jscodeshift handles the separator transformation structurally
root.find(j.ObjectTypeAnnotation).forEach(path => {
path.node.properties.forEach(prop => {
// comma-to-semicolon transformation applied here
});
});
return root.toSource({ quote: 'single' }); // emit transformed source
// If tsc --noEmit passes on this file: transformation is correct
// If it fails: a new edge case has been discovered for the rule set
};
The codemod development phase was not a weekend project — it was months of careful engineering. The transformation rules were tested against the actual codebase incrementally, with TypeScript compilation as the correctness oracle. Each failing compilation revealed another edge case for the codemod to handle. By migration day, the codemod had been proven against the full diversity of patterns present in 3.7 million lines.
The suppressions problem: migrating silence
Both Flow and TypeScript support type error suppression comments — a way to tell the type checker to ignore a specific error. These comments have different syntax in Flow (// $FlowFixMe) versus TypeScript (// @ts-ignore). Correctly migrating suppressions required not just syntax transformation but understanding what the suppression was suppressing and whether the equivalent TypeScript error existed. Some suppressions could be removed because TypeScript handled the case correctly; others needed the equivalent TypeScript suppression syntax.
Why Sunday was the right day
Launching a 3.7-million-line migration on a Sunday was a deliberate risk reduction strategy. Sunday has the lowest deploy frequency and the lowest traffic of any day in Stripe's week — meaning if something went wrong with the TypeScript configuration, there was less production code running that might be affected, and engineers could address issues before the Monday morning rush. The Sunday timing transformed a potentially chaotic migration into a calm, controllable event.
The risk of atomic migration: no partial rollback
The single-PR atomic approach eliminates mixed state but also eliminates partial rollback. If the TypeScript configuration had a subtle misconfiguration affecting production builds, the only option was to revert the entire migration PR — 3.7 million lines back to Flow in one operation. Stripe mitigated this by running the TypeScript configuration in CI for weeks before migration day, ensuring the build system was proven before the code was switched. Atomic migrations require particularly thorough pre-migration validation.
Architecture
The Flow-to-TypeScript migration is architecturally different from most large-scale engineering changes — it's a developer tooling migration rather than a production infrastructure change. But it shares the same core challenges: a large, live system needs to change; incremental migration creates dangerous mixed states; automation is the only viable path at scale. The architectural patterns — build the transformation machine, use compilation as the correctness oracle, execute atomically — are directly applicable to infrastructure and data migrations as well.
Codemod Development and Execution Pipeline
View interactive diagram on TechLogStack →
Interactive diagram available on TechLogStack (link above).
Before and After: Type System Ecosystem Position
View interactive diagram on TechLogStack →
Interactive diagram available on TechLogStack (link above).
Lessons
Large-scale code transformations require automation, not heroics. 3.7 million lines cannot be migrated manually with consistency. Build the codemod first. The investment in automation tooling is repaid by the quality, consistency, and speed of the transformation it enables.
Use compilation as your correctness oracle during codemod development. Running the type checker against migrated code subsets gives immediate, objective feedback on transformation correctness — more reliable than manual code review of thousands of transformed files.
Atomic migration (executing a complete migration in a single deployment rather than gradually, eliminating any intermediate mixed-state period) is preferable to incremental migration when the mixed state creates engineering overhead. A gradual Flow-to-TypeScript migration would require supporting both type checkers simultaneously for months. The single-PR atomic approach eliminated that overhead entirely.
Technical debt in developer tooling compounds in ways that are easy to underestimate. Every month Stripe stayed on Flow was a month of suboptimal IDE tooling, missing ecosystem support, and recruiting friction for candidates who expected TypeScript. Quantify developer tooling debt explicitly — the compounding cost is real even when it's not directly measurable in production incidents.
Prepare your team for an abrupt transition, not a gradual one. Good internal documentation and onboarding resources matter more for atomic migrations than gradual ones — engineers go from the old system to the new system overnight, and the organisation needs to support that transition actively.
Engineering Glossary
AST (Abstract Syntax Tree) — a tree representation of code's structure that enables programmatic analysis and transformation without working with raw text strings. AST-based codemods can make structurally correct transformations in context, handling multi-line annotations and nested types that text-based search-and-replace cannot.
Atomic migration — executing a complete migration in a single deployment rather than gradually, eliminating any intermediate mixed-state period. Stripe's 3.7-million-line migration was atomic: one PR, one deployment, one day.
Codemod — an automated code transformation tool that parses source code into an AST, applies structural transformation rules, and emits the modified code while preserving formatting. The correct approach for any code migration too large to execute manually with consistency.
Flow — Facebook's open-source JavaScript type checker with its own annotation syntax, requiring the Flow type checker rather than TypeScript's tsc. Was Stripe's type system of choice until 2022, when TypeScript had become the overwhelming industry standard.
jscodeshift — an open-source JavaScript codemod toolkit from Facebook that parses code into an AST, provides a jQuery-like API for traversing and transforming it, and prints the modified AST back to source code while preserving formatting. The framework Stripe used to build their Flow-to-TypeScript codemod.
Mixed state — the condition where a codebase contains files in two different formats (some Flow, some TypeScript) simultaneously, requiring both type checkers to run in CI and creating confusion for engineers working across files. The primary reason Stripe chose an atomic single-PR migration over gradual rollout.
This case is a plain-English retelling of publicly available engineering material.
Read the full case on TechLogStack →
(Interactive diagrams, source links, and the full reader experience)
TechLogStack — built at scale, broken in public, rebuilt by engineers.
Top comments (0)