This document covers breaking changes introduced across TypeScript 5.0 through 5.9 that are likely to surface during a routine upgrade. These accumulate — upgrading from 5.0 to 6.0 means encountering all of them.
All enums are now union enums
Every enum member now gets its own unique type. Previously only enums with literal initializers were treated as union enums. This can break code that assigns computed/non-literal enum values across enum boundaries.
|
|
Fix: use an explicit cast e = n as E or restructure the code.
--module and --moduleResolution must agree on Node.js settings
When using node16 or nodenext for either --module or --moduleResolution, the other must also use a Node.js-related setting. Mismatches like --module esnext --moduleResolution node16 are now rejected.
Fix: use --module nodenext alone, or use --module esnext --moduleResolution bundler.
Consistent export checking for merged symbols in ambient contexts
When two declarations merge in ambient contexts (declaration files, declare module blocks), they must now agree on whether they are both exported.
|
|
Fix: add export to both, or remove export {} so all declarations are implicitly exported.
super property accesses on instance fields now error
TypeScript now errors when super.x refers to a class field (as opposed to a prototype method), since this is undefined at runtime.
|
|
This is always a usercode bug.
More accurate conditional type constraints
A conditional type like T extends Foo ? A : B where T is generic no longer eagerly resolves to the false branch when T’s constraint doesn’t extend Foo. It produces a union instead.
|
|
Fix: don’t assume the conditional type resolves to a single branch when the type parameter is generic. This will likely be a complicated fix.
More aggressive reduction of intersections with type variables and primitives
Intersections of type variables with primitives are now reduced more aggressively. T & number becomes never if T’s constraint is known to be incompatible with number.
|
|
The correct fix is context-dependent.
Errors when type-only imports conflict with local values (under isolatedModules)
A type-only import that has the same name as a local value declaration is now an error:
|
|
Fix: add the type modifier to the import, or rename the local variable.
Enum members cannot be named Infinity, -Infinity, or NaN
|
|
This is always a usercode bug.
Inferred type predicates from filter-like callbacks
TypeScript now infers type predicates for filter-like functions. Code that relied on the wider pre-filter type may break:
|
|
Fix: add an explicit type annotation if the wider type was intended instead.
Regular expression syntax checking
Invalid patterns, non-existent backreferences, and features unavailable at the configured target now produce errors:
|
|
Fix: correct the regex, if the intended meaning was clear, or upgrade target to the necessary ES version that supports the needed regex feature. Otherwise, treat as a usercode bug.
Disallowed nullish and truthy checks
Expressions that are syntactically always truthy or always nullish now error:
|
|
If the intended meaning was obvious (usually missing parens), fix it, otherwise flag as a usercode bug.
Respecting file extensions and package.json inside node_modules
TypeScript now reads .mjs/.cjs extensions and package.json "type" fields inside node_modules in all module modes, not only under node16/nodenext. Previously-valid default imports from ESM packages may now error:
|
|
Fix: use namespace import import * as dep from "dep" or switch module settings.
Correct override checks on computed properties
Computed properties marked override now properly check for the existence of a base class member, and noImplicitOverride now catches missing override on computed properties that shadow base members.
Checks for never-initialized variables
Variables that are never initialized (not just possibly uninitialized) now error when accessed from nested functions:
|
|
Unless the fix is obvious, treat this as a usercode bug.
TypedArrays are now generic over ArrayBufferLike
All TypedArray types (e.g. Uint8Array) now have a type parameter for the buffer type. This can break code using Node.js Buffer:
error TS2322: Type 'Buffer' is not assignable to type 'Uint8Array<ArrayBufferLike>'.
Fix: update @types/node to the latest version.
Validated JSON imports in --module nodenext
JSON file imports now require an import attribute with type: "json":
|
|
Also, named exports from JSON are no longer available; use the default import instead.
Granular checks for branches in return expressions
Each branch of a conditional expression in a return statement is now independently checked against the return type:
|
|
Unless the fix is obvious, treat this as a usercode bug.
Import assertions replaced by import attributes
The assert keyword is no longer accepted; use with:
|
|
TypeScript 6.0 Migration
TypeScript 6.0 changes many defaults. When upgrading to TS 6.0, you will want to explicitly set any config value that would be inferred differently to avoid unwanted changes.
| Setting | Old Default | New Default | Quick Fix |
|---|---|---|---|
strict |
false |
true |
Add "strict": false to restore old behavior |
types |
["*"] (all @types) |
[] (none) |
Depends |
rootDir |
inferred from sources | . (tsconfig directory) |
Add "rootDir": "./src" |
module |
commonjs |
esnext |
Determine correct setting based on project |
target |
es5 |
es2025 |
Set to ES6 if the project really needs to target ancient stacks |
noUncheckedSideEffectImports |
false |
true |
Set to false to restore old behavior if needed |
libReplacement |
true |
false |
Set to true to restore old behavior if needed |
During 6.0 migration, you must check rootDir. If all TS files are under src, you must set rootDir since otherwise TS will output to e.g. ./outDir/src instead.
types directives
With types defaulting to [], you may need to add entries to this array depending on the intended global environment:
Buffer, modules like"fs","path", etc: Add"node"describe,it, etc: add the correct test framework ("jest","mocha", etc)
'rootDir' is expected to contain all source files**
Explicitly add the appropriate rootDir
|
|
Error: Option 'moduleResolution' with value 'node10' is deprecated** (or classic)
For Node.js projects:
|
|
For web projects using a bundler:
|
|
Error: Option 'baseUrl' is deprecated**
If you can simply remove baseUrl, do. However, if the tsconfig has paths entries, run npx -y @andrewbranch/ts5to6 --fixBaseUrl tsconfig.json to fix path mappings automatically
Error: Option 'outFile' is deprecated
Abort and tell the user they need to use their own bundler now.
Error: Option 'module' with value 'amd' is deprecated** (or system, umd):
Abort and tell the user they need to pick a new module format
Option 'esModuleInterop' cannot be set to 'false' (or allowSyntheticDefaultImports)
These are always-on in TypeScript 6.0. Remove the explicit false setting. You may need to update imports:
|
|
Option ‘downlevelIteration’ is deprecated`
Only had effect on ES5 emit, which is also deprecated. Remove the setting from the config entirely.
TS2307 on private package subpaths
Symptom: After switching moduleResolution to bundler (or node16 / nodenext), imports that reach into a package’s internals fail:
src/utils/zod.ts(3,26): error TS2307: Cannot find module 'zod/v4/core/util.cjs'
Under these resolvers TypeScript honors the package’s exports map, so subpaths that the package does not publish are no longer reachable — even if the file exists on disk.
Fix:
Find the public exports-mapped path that exposes the same symbol.
Every well-maintained package publishes types through its exports map — the private subpath exists only for the package’s own internal use. Look in order:
- Check the package’s top-level export (
import { X } from 'pkg'). - Check documented sub-entry-points in the
exportsfield ofpackage.json(e.g.zod/v4,zod/v4/core). - Run
node -e "console.log(Object.keys(require('pkg/package.json').exports))"to see all published paths at once. - Search the package’s type declaration files for the symbol name to locate which public entry re-exports it.
Do not fall back to inlining a local copy of the type.
That creates drift and hides the real fix.
If you cannot locate a public re-export after the steps above, open the package’s repo and search its exports map directly — it is almost always there under a different entry point than the one the code was using.
Do not work around this by disabling exports resolution or downgrading moduleResolution.