Stripe · Performance · 17 May 2026
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.
- 3.7M lines converted in 1 PR
- Single Sunday deploy
- Largest JS codebase at Stripe
- Automated AST-based migration
- Codemod tooling built internally
- Zero Flow type errors after switch
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. The migration had been planned and built over months. Its execution took one day. 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.
📝
Flow (Facebook's JavaScript type system — an alternative to TypeScript that checks types statically but uses its own annotation syntax and requires the Flow type checker rather than TypeScript's tsc) had been Stripe's type system of choice when their codebase was first typed. By 2022, TypeScript had become the overwhelming industry standard, with a richer ecosystem, better IDE tooling, and more active development. Flow's community was contracting while TypeScript's was exploding.
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 that compiles to plain 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 a migration debt that would only grow harder to pay.
THE AUTOMATION IMPERATIVE
Manual migration of 3.7 million lines would require years of engineer time and create an inconsistent, error-prone result with different teams applying different migration patterns. The only viable approach was building a fully automated migration tool — an AST-based codemod (a program that parses source code into an Abstract Syntax Tree, performs structural transformations on the tree, then emits the modified code — preserving formatting and only changing what needs to change) that could parse every Flow-annotated file and emit a valid TypeScript equivalent.
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 on Flow-typed code 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 — an automated code transformation tool — 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 the 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.
ℹ️
Flow and TypeScript: The Annotation Differences
Flow and TypeScript share a common lineage — both add type annotations to JavaScript using a similar syntax. But they diverge in meaningful ways: Flow uses type declarations differently, handles null/undefined with its own operators, has its own syntax for type imports, and uses a different comment format for suppressing type errors. Each of these differences required a transformation rule in the codemod, and edge cases accumulated quickly across 3.7 million lines.
The codemod development phase was not a weekend project — it was months of careful engineering. Stripe's team had to map every Flow annotation pattern to its TypeScript equivalent, handle edge cases and ambiguous cases, verify the transformation preserved semantic meaning, and run the tool against subsets of the codebase to validate correctness before trusting it on the full 3.7 million lines. The transformation rules were tested against the actual codebase incrementally, with TypeScript compilation as the correctness oracle: if the converted code compiled without type errors, the transformation was correct. Each failing compilation revealed another edge case for the codemod to handle.
⚠️
The Suppressions Problem
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.💡
The One-PR Strategy: Atomic Consistency
A single atomic pull request was the only way to ensure consistent migration state. If the migration were rolled out gradually — file by file or team by team — the codebase would be in a mixed state with some files using Flow syntax and others TypeScript syntax. This mixed state would require supporting both type checkers simultaneously, create confusion for engineers working across files, and extend the migration timeline to months. The single-PR atomic approach eliminated the mixed state entirely.
✅
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.
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 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 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 the migration day, ensuring the build system was proven before the code was switched. Atomic migrations require particularly thorough pre-migration validation.
The Fix
The Codemod: Engineering the Migration Machine
The codemod that performed the migration 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 (a JavaScript codemod toolkit 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, transformed in a single automated operation
- 1 PR — Deployment vehicle for the entire migration — ensuring atomic, consistent state across all 3.7 million lines simultaneously
- 1 day — Execution time of the migration on March 6, 2022 — 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 examples:
// type Props = { name: string, age: number }
// const foo = (x: ?string) => x // ?string = nullable in Flow
// import type { User } from './types' // Flow 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!
// Simplified codemod rule for nullable type conversion:
module.exports = function transformer(file, api) {
const j = api.jscodeshift;
const root = j(file.source);
// Find all nullable type annotations: ?SomeType
root.find(j.NullableTypeAnnotation).replaceWith(path => {
// Replace ?T with T | null | undefined (TypeScript union)
return j.unionTypeAnnotation([
path.node.typeAnnotation, // the original T
j.nullLiteralTypeAnnotation(), // null
]);
});
// Find Flow object type separators (commas) and replace with TypeScript (semicolons)
root.find(j.ObjectTypeAnnotation).forEach(path => {
path.node.properties.forEach(prop => {
// jscodeshift handles the comma-to-semicolon transformation
});
});
return root.toSource({ quote: 'single' }); // emit transformed source
};
COMPILATION AS THE CORRECTNESS ORACLE
The migration team used TypeScript compilation (
tsc --noEmit) as the primary correctness oracle for the codemod. A successfully compiled file meant the transformation was semantically correct. A compilation error meant the codemod had produced invalid TypeScript — revealing a missing transformation rule or an edge case. Running tsc against incrementally migrated subsets of the codebase was the primary quality loop for codemod development , more reliable than manual code review of thousands of transformed files.✅
Monday Morning: Hundreds of Engineers, New Language
The day after the migration, hundreds of Stripe engineers arrived to find their codebase in TypeScript. No ramp-up period, no gradual transition, no mixed state to navigate. TypeScript was simply the language from that day forward. The abrupt transition required good internal documentation and TypeScript onboarding resources, but the absence of a prolonged mixed-state period was a net engineering productivity gain — engineers could learn one thing instead of learning two systems simultaneously.
ℹ️
The TypeScript Ecosystem Advantage
Post-migration, Stripe engineers gained the full TypeScript ecosystem advantage: TypeScript-first type definitions for most open-source libraries, significantly better IDE autocomplete and inline error reporting in VS Code, and compatibility with the rest of the industry's tooling. The tooling quality difference between Flow and TypeScript by 2022 was substantial — the migration unlocked a persistent daily productivity improvement for hundreds of engineers working in the codebase.
ℹ️
Codemod Iteration: Months Before the Sunday
The codemod was not built once and deployed — it was iterated. The team ran early versions against small subsets of the codebase, examined the output, identified missed cases, added transformation rules, and repeated. Each iteration against real production code revealed patterns that weren't in the test cases. This iterative refinement against the actual target codebase is what made the Sunday execution clean — by migration day, the codemod had been proven against the full diversity of patterns present in 3.7 million lines.
THE TALENT PIPELINE ARGUMENT
By 2022, the typical new-hire JavaScript engineer expected TypeScript. Flow-only codebases were increasingly unfamiliar to engineers coming from bootcamps, universities, and other major tech companies. The migration was a recruiting and onboarding investment as much as a tooling investment — reducing the friction of ramping up new engineers on Stripe's frontend codebase.
Architecture
The Flow-to-TypeScript migration is architecturally different from most of the case studies in this collection — 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).
AST TRANSFORMATIONS: THE POWER AND THE RISK
AST-based code transformations are powerful because they operate on the semantic structure of code , not on raw text. A text-based search-and-replace would fail on multi-line type annotations, nested generics, and comments adjacent to type syntax. An AST transformation understands the code's structure and can make correct transformations in context. The risk: AST transformation rules must be exhaustive for the patterns present in the codebase, or the generated code will have subtle errors that only appear at runtime or in edge cases.
⚠️
What the Codemod Couldn't Do
Automated codemods handle syntax transformation perfectly but cannot handle semantic meaning changes. In a few cases, Flow and TypeScript's type systems make different assumptions about the same code — a type that Flow considers valid that TypeScript considers an error, or vice versa. These cases required manual review after the automated migration. The codemod was the 99% solution; the manual cleanup handled the remaining 1% of cases that required human judgment.
ℹ️
jscodeshift: The Transformation Framework
jscodeshift (an open-source JavaScript codemod toolkit created by Facebook that provides a jQuery-like API for traversing and transforming JavaScript ASTs) was the foundation for Stripe's codemod. It handles parsing, AST traversal, and code emission while allowing engineers to focus on writing transformation logic. The framework's familiarity (JavaScript-based, with a well-documented API) meant Stripe's frontend engineers could contribute to the codemod without learning a new tooling ecosystem.
Lessons
Stripe's Flow-to-TypeScript migration is the case for investing in automation tooling before attempting any large-scale code transformation. The migration itself took one day. Building the migration tool took months. That ratio is correct.
- 01. 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.
- 02. 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.
- 03. 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.
- 04. 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.
- 05. 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 organization needs to support that transition actively.
⚠️
Document the Decision Before You Ship
A migration that affects hundreds of engineers needs documentation ready before the PR merges. Stripe prepared TypeScript onboarding materials, answered common questions about the syntax differences, and communicated the migration plan and rationale to engineering broadly before the Sunday execution. Engineers who arrived Monday morning to a new type system should not be the first people asking 'wait, what happened?'
THE REAL ROI OF TYPE SYSTEM INVESTMENT
The business case for Flow-to-TypeScript is rarely made in terms of production incidents prevented — type systems catch compile-time errors that never reach production. The ROI is in engineering velocity : faster development cycles, fewer bugs caught late in review or in staging, better IDE-assisted refactoring, and lower onboarding cost for new engineers. These are real but diffuse benefits that require organizational commitment to measure and communicate.
Building the codemod took months. Running it took one day. The engineers who merged the PR that Sunday probably didn't fully appreciate they were doing the most leverage-per-keystroke work of the entire project.
TechLogStack — built at scale, broken in public, rebuilt by engineers
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).
Top comments (0)