TypeScript 7 Migration: Fix Your tsconfig First

A TypeScript 7 migration is mostly a tsconfig.json job. The Go rewrite shipped on 8 July 2026 as [email protected], under the same tsc name and with the same type checker, so your existing errors should not move. What changes is configuration: a dozen deprecated options are now hard failures, eight defaults flipped, and there is no compiler API yet.

That last point decides the order you do things in. Everything below comes from the TypeScript 7.0 and TypeScript 6.0 release posts, checked against the npm registry on 25 August 2026, when typescript was on 7.0.2 and @typescript/typescript6 on 6.0.2.

Which options break a TypeScript 7 migration?

Every option TypeScript 6.0 marked deprecated is gone. Not warned about. Gone.

target: "es5" and downlevelIteration are the two that catch old codebases first, and there is no replacement for either: if you still need ES5 output, a bundler or SWC has to do the downlevelling. moduleResolution: "node" (also spelled node10) and moduleResolution: "classic" are both rejected, so you move to nodenext or bundler. The module values amd, umd, system and none are rejected too.

baseUrl is the one that quietly costs an afternoon. It no longer exists, so every entry in paths has to be rewritten relative to the tsconfig file itself rather than to some inner source directory. Three flags can also no longer be turned off: esModuleInterop, allowSyntheticDefaultImports and alwaysStrict are all fixed at true.

A few syntax-level rules moved with them. The module keyword form of namespace declarations is rejected in favour of namespace. Import assertions are gone, so assert { type: "json" } becomes with { type: "json" }. /// <reference no-default-lib="true" /> is ignored when skipDefaultLibCheck is on. And passing file paths on the command line while a tsconfig.json sits in scope is now an error unless you add --ignoreConfig, which is the fix for the tsc src/one-file.ts habit that silently ignored your config for a decade.

A config that looked like this in 5.x:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "moduleResolution": "node",
    "baseUrl": "./src",
    "paths": { "@app/*": ["app/*"] },
    "esModuleInterop": false,
    "downlevelIteration": true,
    "outDir": "./dist"
  }
}

is roughly this under 7.0:

{
  "compilerOptions": {
    "module": "nodenext",
    "moduleResolution": "nodenext",
    "paths": { "@app/*": ["./src/app/*"] },
    "rootDir": "./src",
    "types": ["node"],
    "outDir": "./dist"
  }
}

Which tsconfig defaults changed?

Eight of them, and two will surprise you.

rootDir now defaults to ./, the directory holding the tsconfig file, rather than being inferred from your input files. If your sources live in src/ and your config does not, your emitted output gains a src/ level it did not have before. Set rootDir explicitly and the problem disappears.

types now defaults to []. Previously every @types package under node_modules was pulled into the global scope whether you asked or not. Now you list them, which is better hygiene and an immediate wall of Cannot find name 'process' if you upgrade without reading this.

The rest are easier. strict is true, module is esnext, target is the most recent stable ECMAScript version, noUncheckedSideEffectImports is true, and libReplacement is false. stableTypeOrdering is true and cannot be switched off, which is what makes union and intersection ordering deterministic between runs.

Do I need to go through TypeScript 6.0 first?

It is the cheap way to do it. TypeScript 6.0 landed on 23 March 2026 and exists for exactly this: it is the last JavaScript-based release, and it warns about everything 7.0 removes instead of failing on it. Upgrade to 6.0, clear the deprecation warnings, then bump the major.

Two flags there earn their keep. "ignoreDeprecations": "6.0" buys you time on a config you cannot fix this sprint, and --stableTypeOrdering opts you into 7.0’s deterministic type ordering early so that error-message diffs between the two compilers stay readable. Microsoft puts the cost of that second one at up to 25% on 6.0, so use it on a branch rather than in CI.

What still needs TypeScript 6.0 after you upgrade?

Anything that imports the compiler. TypeScript 7.0 ships without a public API, and the team has said a new one is planned for 7.1. Until then the Volar-based tooling behind Vue, Astro, Svelte and MDX, plus Angular’s template type-checking, all need 6.0.

The npm metadata says the same thing more bluntly. As of 25 August 2026, typescript-eslint 8.68.0 declares a peer range of >=4.8.4 <6.1.0, ts-jest 29.4.12 declares >=4.3 <7, typedoc 0.28.20 tops out at 6.0.x, and @astrojs/check 0.9.10 accepts ^5.0.0 || ^6.0.0. Install 7.0 alone in one of those repos and your package manager will tell you about it.

The supported answer is to run both, which is why Microsoft publishes @typescript/typescript6. It provides a tsc6 binary that does not collide with 7.0’s tsc:

{
  "devDependencies": {
    "typescript": "npm:@typescript/typescript6@^6.0.2",
    "@typescript/native": "npm:typescript@^7.0.2"
  },
  "scripts": {
    "typecheck": "tsc --noEmit",
    "lint": "eslint ."
  }
}

Tools that require("typescript") resolve to 6.0 and keep working. Your type-check script runs the Go compiler. When 7.1 gives the ecosystem an API to build against, you delete the alias.

Is it actually ten times faster?

On full builds, close to it. Microsoft’s published figures put vscode at 125.7s under 6.0 and 10.6s under 7.0, sentry at 139.8s to 15.7s, playwright at 12.8s to 1.47s, and tldraw at 11.2s to 1.46s, with memory down between 6% and 26%. The editor numbers are the ones you feel hourly: time to show errors across vscode fell from 17.5 seconds to 1.3.

Three new flags control the parallelism. --checkers sets the number of type-checking workers and defaults to 4, --builders sets how many project references build at once under --build, and --singleThreaded turns all of it off for debugging or a memory-limited CI box. They multiply, so --build --builders 4 --checkers 4 can put sixteen checkers in flight at once:

tsc --build --builders 4 --checkers 2

Watch mode was rebuilt on a Go port of Parcel’s file watcher, so the polling fallbacks that made tsc --watch expensive on large trees are gone.

What changes in the types themselves?

Type checking is a port of 6.0’s, but two behaviours moved on purpose.

Template literal types now split strings by Unicode code point instead of UTF-16 code unit, which aligns them with for...of and spread:

type HeadTail<S extends string> = S extends `${infer Head}${infer Tail}`
  ? [Head, Tail]
  : never;

// "\u{1F600}abc" is one emoji followed by "abc".
type Result = HeadTail<"\u{1F600}abc">;

// TypeScript 6.0: ["\ud83d", "\ude00abc"]   two surrogate halves
// TypeScript 7.0: ["\u{1F600}", "abc"]      one code point

The other is JSDoc. Checked JavaScript is now analysed much more like TypeScript, so a value can no longer stand in for a type (use typeof value), @enum is no longer recognised, a bare ? is not any, @class no longer turns a function into a constructor, postfix ! is out, and Closure-style function types such as function(string): void are unsupported. If you run checkJs over a large JavaScript codebase, budget for this separately from the tsconfig work.

Library authors have one more thing to check: declaration emit in the native compiler differs from 6.0 in places, deliberately. Generate your .d.ts files with both and diff them before you publish.

What I would do

Upgrade to 6.0 now and treat its deprecation warnings as a work item, because that work is unavoidable and it is cheaper before the compiler starts failing. If your repo is plain TypeScript with a Node or bundler pipeline, go to 7.0 straight after and take the build-time win, which is real. Our TypeScript consultancy work has hit the rootDir and types defaults on almost every project we have moved, so change those two first and read the diff in your output directory.

If you ship Vue, Astro, Svelte or Angular, stay on 6.0 for the moment and revisit when 7.1 ships the API. A repo already running Node’s built-in type stripping is the easiest case of all, since tsc there is only a checker and nothing downstream depends on its emit. And if you are cleaning up tooling anyway, the same argument that pushed us to compare oxlint against Biome applies here: the Go and Rust rewrites are only worth adopting where the tools around them have caught up.

Need this built properly?

Whoooop Ltd has spent 15+ years building and maintaining web applications in TypeScript, React, Node.js and serverless — the same ground this post covers.

Get in touch