TypeScript 6.0: What Shipped, What’s Stable, and What Will Break Your Build

TypeScript 6.0 landed in March 2026, and unlike most minor-version releases, this one has teeth. It’s the last version of the compiler written in JavaScript — version 7.0 will be a full rewrite in Go — and the team used this moment to clean house on a decade’s worth of accumulated defaults and legacy options.

The new features are genuinely good. The breaking changes are real and will catch teams off guard. The defaults shift is the kind of thing that silently breaks tsc --noEmit in CI at 2am on a Tuesday.

Let’s go through all of it without hand-waving.


Why 6.0 Is Different From a Typical Release

Most TypeScript releases follow a comfortable pattern: new type-level features, stricter inference, maybe a new utility type. You upgrade, run the compiler, fix fifteen unknown errors, ship it.

TypeScript 6.0 breaks that pattern. It’s explicitly a bridge release — its job is to deprecate or remove everything that TypeScript 7.0 (the Go-based native compiler) won’t support. The team gave developers a release cycle to adjust before 7.0 yanks the rug entirely.

The signal here: if you’re still using --moduleResolution node, --outFile, --target es5, or any AMD/UMD modules, TypeScript 6.0 is warning you. TypeScript 7.0 will not warn you — it will just refuse.

Official GitHub: microsoft/TypeScript


New Features Worth Actually Using

Less Context-Sensitivity on this-less Functions

This one has been a long-standing papercut. In previous TypeScript versions, method syntax in object literals could confuse generic inference depending on property order. The following would fail if consume came before produce:

declare function callIt<T>(obj: {
    produce: (x: number) => T,
    consume: (y: T) => void,
}): void;

// Before 6.0: would fail with method syntax if properties were out of order
callIt({
    consume(y) { return y.toFixed(); }, // inferred 'y: number' correctly now
    produce(x: number) { return x * 2; },
});

The fix: functions that don’t use this are no longer treated as contextually sensitive during inference. The ordering of properties no longer breaks type inference. This is small but it removes an entire class of "why does this fail when I swap these two lines" confusion.

Temporal API Types — Finally

The Temporal proposal hit Stage 4, and TypeScript 6.0 ships built-in types for it. No more @types/temporal-polyfill or hand-rolling declarations.

const now = Temporal.Now.instant();
const tomorrow = now.add({ hours: 24 });
const yesterday = now.subtract({ hours: 24 });

// Duration, ZonedDateTime, PlainDate — all typed
const meeting = Temporal.PlainDateTime.from("2026-06-01T09:00");

Browser support is still uneven, so you’ll need a polyfill in most environments. But having the types officially in the compiler means your IDE tooling just works.

Map/WeakMap getOrInsert and getOrInsertComputed

The "upsert" pattern for Maps has always been verbose. TypeScript 6.0 types the new ECMAScript proposal methods:

// Old pattern — annoying boilerplate
let val: string;
if (cache.has(key)) {
    val = cache.get(key)!;
} else {
    val = computeDefault();
    cache.set(key, val);
}

// TypeScript 6.0 — typed and clean
const val = cache.getOrInsert(key, computeDefault());

// Lazy version for expensive computations
const val = cache.getOrInsertComputed(key, (k) => expensiveOp(k));

These live in the esnext lib target, so set "lib": ["esnext"] or higher to get them.

RegExp.escape()

You’ve either written a escapeRegExp utility function a dozen times, or you’ve copied it from Stack Overflow. Both are now unnecessary:

function highlight(term: string, text: string) {
    const pattern = new RegExp(RegExp.escape(term), "gi");
    return text.replace(pattern, `<mark>$&</mark>`);
}

Also lives in esnext lib.

ES2025 Target and Lib

"target": "es2025" is now a valid option and is the new default. It brings Promise.try, Iterator methods, Set operations (union, intersection, difference), and RegExp.escape into the typed surface area.

Subpath Imports With #/

Node.js added a shorthand for subpath imports — instead of defining "#root/*": "./dist/*", you can map #/* directly:

// package.json
{
  "imports": {
    "#/*": "./dist/*.js"
  }
}
// your code
import { logger } from "#/utils/logger.js";

TypeScript 6.0 resolves these correctly under nodenext and bundler module resolution. This is cleaner than path aliases and works natively with Node’s module system.

DOM iterable Merged Into dom

Previously, if you wanted to iterate DOM collections, you needed:

"lib": ["dom", "dom.iterable"]

In TypeScript 6.0, dom already includes iterable and async-iterable content. Your querySelectorAll results, NodeList, HTMLCollection — all iterable by default. Simplifies tsconfig for browser projects.

--moduleResolution bundler + --module commonjs

Previously a forbidden combination. Now allowed, specifically to give teams a migration path off deprecated --moduleResolution node. You can keep CJS output while getting bundler-style module resolution semantics during the transition period.


The Defaults Changed. This Will Break Things.

Here’s the table that’s going to generate the most Stack Overflow questions over the next six months:

Option Old Default New Default
strict false true
module commonjs esnext
target es5 es2025
types all @types/* [] (none)
rootDir inferred from files . (tsconfig directory)
noUncheckedSideEffectImports false true
libReplacement true false

If your project has an explicit tsconfig.json with all these set already, you’re probably fine. If you’ve been relying on implicit defaults anywhere — especially strict, module, or types — you need to audit.

The types: [] Default Is the Silent Killer

This is the one that will catch the most people. TypeScript previously auto-discovered all installed @types/* packages and included them globally. That’s how process.env.NODE_ENV just worked without you ever setting "types": ["node"] in your tsconfig.

In TypeScript 6.0, types defaults to an empty array. You must declare your global type packages explicitly:

{
  "compilerOptions": {
    "types": ["node", "jest", "react"]
  }
}

The upside is real: projects that tested this saw 20–50% build time improvements because the compiler was no longer ingesting hundreds of @types packages it didn’t need. The downside: if you don’t add this, code that referenced global types from unrelated packages will start throwing errors.

Gotcha: Monorepos are particularly exposed here. Each workspace tsconfig needs its own types list. Don’t just fix the root.


What’s Deprecated (And Gone in 7.0)

These aren’t soft deprecations. Each of these will be completely removed in TypeScript 7.0. You can silence the warnings now with "ignoreDeprecations": "6.0", but that setting won’t work in 7.0 — at that point, your build simply breaks.

target: es5 — Dropped

The minimum supported target is now ES2015. If you’re still targeting IE11 or ancient Node versions, TypeScript 6.0 will warn; TypeScript 7.0 will error.

What to do: Use esbuild, Babel, or SWC as a second pass to downlevel to ES5 if you genuinely need it. TypeScript was never great at this anyway — the output was verbose and slow compared to dedicated transpilers.

--moduleResolution node (also called node10) — Deprecated

This is the resolution mode everyone learned TypeScript with. It’s been misleading for years — it doesn’t match how modern Node.js actually resolves modules. Time’s up.

Migration path:

  • Node.js projects: switch to --moduleResolution nodenext and set --module nodenext
  • Bundler projects (Webpack, Vite, Rollup): switch to --moduleResolution bundler
// For Node.js applications
{
  "compilerOptions": {
    "module": "nodenext",
    "moduleResolution": "nodenext"
  }
}

// For bundler-based (Vite, Webpack, etc.)
{
  "compilerOptions": {
    "module": "esnext",
    "moduleResolution": "bundler"
  }
}

--module amd, umd, systemjs, none — Gone

AMD and UMD are legacy module formats from the RequireJS era. SystemJS had a brief moment of relevance with Angular. None of them matter in 2026.

If you’re somehow still emitting AMD or UMD, TypeScript is not the right tool to produce that output — use a bundler that’s purpose-built for it.

--outFile — Gone

--outFile concatenated your TypeScript output into a single file. It only worked with AMD/SystemJS, which are also gone. Use a real bundler.

--baseUrl as Module Resolution Root — Deprecated

baseUrl was a common hack to get import { foo } from "utils/bar" to resolve relative to src/. It worked by making the baseUrl a resolution root.

That behavior is gone. Use paths with explicit mapping instead:

// Before
{
  "compilerOptions": {
    "baseUrl": "./src",
    "paths": {
      "@app/*": ["app/*"]
    }
  }
}

// After
{
  "compilerOptions": {
    "paths": {
      "@app/*": ["./src/app/*"],
      "utils/*": ["./src/utils/*"]
    }
  }
}

baseUrl still works for its documented purpose (resolving relative imports), but using it as an implicit module root is deprecated.

esModuleInterop: false — Deprecated

You can no longer disable esModuleInterop. The safer interop behavior is now always on. The practical consequence: the import * as X from "module" pattern for CommonJS modules should become import X from "module".

// Deprecated pattern (still works with ignoreDeprecations in 6.0)
import * as express from "express";
const app = express();

// Correct pattern
import express from "express";
const app = express();

Gotcha: Some older @types packages only expose a namespace export and don’t have a proper default export. You may hit Module has no default export errors. Check the types and file an issue with upstream if needed.

module Foo {} Namespace Syntax — Deprecated

The module keyword was repurposed early in TypeScript’s history to mean "namespace." It’s now unambiguously just ES modules. Use namespace instead:

// Deprecated
module MyNamespace {
    export const version = "1.0.0";
}

// Correct
namespace MyNamespace {
    export const version = "1.0.0";
}

Import Assertions (asserts) — Deprecated

The assert import attribute syntax lost the spec race to with. Update your JSON imports:

// ❌ Deprecated
import config from "./config.json" assert { type: "json" };

// ✅ Current
import config from "./config.json" with { type: "json" };

Migration Checklist

Work through this in order. Most projects will hit at least three of these.

// tsconfig.json — minimum you should add before upgrading
{
  "compilerOptions": {
    "strict": true,            // was likely already true in modern projects
    "target": "es2020",        // or whatever actually matches your runtime
    "module": "nodenext",      // or "esnext"/"bundler" depending on your setup
    "moduleResolution": "nodenext",
    "types": ["node"],         // add whatever @types packages you actually need
    "rootDir": "./src",        // be explicit rather than relying on inference
    "esModuleInterop": true,   // no longer optional anyway
    "allowSyntheticDefaultImports": true
  }
}

Steps:

  1. Run tsc --noEmit and collect all errors before touching config
  2. Add "ignoreDeprecations": "6.0" to silence deprecation noise while you fix type errors first
  3. Fix type errors from default changes (especially strict: true newly applying)
  4. Migrate module resolution
  5. Add explicit types array
  6. Fix import syntax (asserts → with, import * as → default imports where applicable)
  7. Remove "ignoreDeprecations" and fix remaining deprecation warnings
  8. Run your full test suite

There’s also a community migration tool, ts5to6, that automates some of the mechanical changes.


The --stableTypeOrdering Flag — For the Cautious

This is a diagnostic tool, not a feature you’ll ship with. Adding --stableTypeOrdering makes TypeScript 6.0 sort types in the same deterministic order that TypeScript 7.0 will use. It costs about 25% in type-check time.

The point: if you’re comparing .d.ts output between your current codebase and the TypeScript 7.0 native compiler preview, this flag reduces noise in the diff. Most teams won’t need it until 7.0 beta testing starts.


What’s Coming in TypeScript 7.0

TypeScript 7.0 is the Go-based native rewrite. The team has been working on it in parallel and released a preview (@typescript/native-preview on npm). Key points:

  • Everything deprecated in 6.0 is removed entirely
  • Type-checking is expected to be significantly faster — benchmarks show 5–10x on large codebases
  • The API surface will change for tooling authors (language server plugins, transformers)
  • strict: true is the only option — there’s no opt-out

If you’re maintaining a TypeScript monorepo or a library that relies on compiler internals, start tracking the 7.0 previews now. The earlier you know what breaks, the less miserable the actual migration is.


TypeScript 6.0 is not a glamorous release, but it’s an honest one. The team used it to do necessary housekeeping that would have been harder to do mid-cycle on 7.0. If you’ve been keeping your tsconfig modern and explicit, the upgrade is straightforward. If you’ve been coasting on implicit defaults from 2018, expect a rough morning.

The trade-off is worth it — the defaults are strictly better, the deprecated options were genuinely dead weight, and the new features (Temporal, getOrInsert, RegExp.escape) reduce real boilerplate. Just don’t upgrade the week before a big release.


Sources:

👁 Views: 202,848 · Unique visitors: 57,406