TypeScript 5.0 was announced on 16 March 2023. It brought two JSDoc tags that JavaScript projects had been asking for: @satisfies, which checks a value against a type without changing the value's own type, and @overload, which gives a function several call signatures.
It also added const type parameters, extends with a list of configs, moduleResolution: bundler with a set of resolution options, and verbatimModuleSyntax, and it deprecated a group of old options that 5.5 then stopped honoring.
At a glance
| Release | TypeScript 5.0 |
|---|---|
| Released | 16 March 2023, the day of its announcement on the TypeScript blog |
| Source | TypeScript 5.0 release notes |
| Examples | Every example runs through TypeScript 5.9.3 when this site is built. |
| Previous release | TypeScript 4.9 |
| Next release | TypeScript 5.1 |
What changed for JavaScript
@satisfies
/** @satisfies {Routes} */ on a declaration, or before a parenthesized expression, checks the value against Routes and reports misspelled or missing properties, while the variable keeps the exact type of its value.
/** @typedef {{ path: string, auth?: boolean }} Route */
/** @satisfies {Record<string, Route>} */
export const routes = {
home: { path: "/" },
account: { path: "/account", atuh: true },error TS2353: Object literal may only specify known properties, and 'atuh' does not exist in type 'Route'.
};jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} @overload
Each JSDoc comment with @overload declares one call signature for the function below it; the last comment, without the tag, types the implementation. Calls are checked against the overloads only.
/** @typedef {{ sku: string, name: string }} Product */
/** @type {Product[]} */
const products = [{ sku: "TEE-01", name: "Cotton tee" }];
/**
* @overload
* @param {string} sku
* @returns {Product | undefined}
*/
/**
* @overload
* @param {string[]} skus
* @returns {Product[]}
*/
/**
* @param {string | string[]} skuOrSkus
*/
export function find(skuOrSkus) {
if (Array.isArray(skuOrSkus)) return products.filter((p) => skuOrSkus.includes(p.sku));
return products.find((p) => p.sku === skuOrSkus);
}
const one = find("TEE-01");
const many = find(["TEE-01", "MUG-07"]);
one?.name;
many.length;type Product = { sku: string; name: string };
const products: Product[] = [{ sku: "TEE-01", name: "Cotton tee" }];
export function find(sku: string): Product | undefined;
export function find(skus: string[]): Product[];
export function find(skuOrSkus: string | string[]) {
if (Array.isArray(skuOrSkus)) return products.filter((p) => skuOrSkus.includes(p.sku));
return products.find((p) => p.sku === skuOrSkus);
}
const one = find("TEE-01");
const many = find(["TEE-01", "MUG-07"]);
one?.name;
many.length;jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} const type parameters
@template const T makes TypeScript infer literal and tuple types for T from the argument, as if the caller had written as const. The release notes show the .ts form; this site checks the JSDoc form with TypeScript 5.9.
/**
* @template const T
* @param {T} routes
* @returns {T}
*/
export function defineRoutes(routes) {
return routes;
}
const routes = defineRoutes({ home: "/", cart: "/cart" });
/** @type {"/cart"} */
const cartPath = routes.cart;
export { cartPath };export function defineRoutes<const T>(routes: T): T {
return routes;
}
const routes = defineRoutes({ home: "/", cart: "/cart" });
const cartPath: "/cart" = routes.cart;
export { cartPath };jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} extends with several configs
extends takes a list. Later configs win where they set the same option, so a project can combine a shared base with a strictness preset.
export function total(lines) {error TS7006: Parameter 'lines' implicitly has an 'any' type.
return lines.length;
}jsconfig.json for this example
{
"extends": [
"./config/base.json",
"./config/strict.json"
],
"include": [
"*.js"
]
} moduleResolution bundler and the resolution options
bundler resolves imports the way Vite, esbuild and webpack do: relative imports without extensions are fine, and package exports are read with the import condition. The same release added allowArbitraryExtensions (types for imports like ./app.css), customConditions, resolvePackageJsonExports and resolvePackageJsonImports.
import { formatPrice } from "./format";
console.log(formatPrice(1999));jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "preserve",
"types": [],
"moduleResolution": "bundler"
},
"include": [
"*.js"
]
} Comparing number | string with a number
>, <, >= and <= between number | string and a number are errors now (TS2365), because one side may be converted at run time. Convert explicitly with Number(limit) or +limit.
/** @param {number | string} limit */
export function overLimitLoose(limit) {
return limit > 4;error TS2365: Operator '>' cannot be applied to types 'string | number' and 'number'.
}
/** @param {number | string} limit */
export function overLimit(limit) {
return Number(limit) > 4;
}jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": [
"*.js"
]
} What did not change
How TypeScript reads the JSDoc tags it already knew is unchanged; the new tags are additions.
verbatimModuleSyntaxkeeps imports and exports as written; in a.jsfile it mainly reports ES module syntax in a CommonJS file (TS1287).
Upgrade notes
From the breaking changes and deprecations:
forceConsistentCasingInFileNamesnow defaults totrue, so an import whose casing differs from the file on disk is an error.--newLinedefaults to LF.- Deprecated:
target: ES3,out,charset,importsNotUsedAsValues,preserveValueImports,keyofStringsOnly,suppressExcessPropertyErrors,suppressImplicitAnyIndexErrors,noImplicitUseStrict,noStrictGenericChecksandprepend."ignoreDeprecations": "5.0"kept them working through 5.4. - TypeScript itself needs an ES2018 runtime (Node.js 10 or later).
Gotchas
@satisfiesbefore an expression needs the parentheses:/** @satisfies {Routes} */ ({ ... }).
Sources
- TypeScript 5.0 release notes · TypeScript handbook
- Announcing TypeScript 5.0 · TypeScript blog
Last reviewed 20 October 2026.