TypeScript 4.4 was announced on 26 August 2021. It added useUnknownInCatchVariables to the strict family, so in a checked .js file under strict, the variable in catch (err) is unknown and has to be narrowed before you read err.message.
It also brought "Did you mean…?" spelling suggestions to JavaScript files that are not checked at all, and narrowing through conditions stored in a const.
At a glance
| Release | TypeScript 4.4 |
|---|---|
| Released | 26 August 2021, the day of its announcement on the TypeScript blog |
| Source | TypeScript 4.4 release notes |
| Examples | Every example runs through TypeScript 5.9.3 when this site is built. |
| Previous release | TypeScript 4.3 |
| Next release | TypeScript 4.5 |
What changed for JavaScript
Caught errors are unknown under strict
JavaScript can throw anything, so TypeScript 4.4 added useUnknownInCatchVariables, which makes every catch variable unknown, and strict turns it on. Reading err.message is then an error (TS18046) until you narrow err, usually with err instanceof Error. Turn useUnknownInCatchVariables off to go back to any.
try {
JSON.parse("{");
} catch (err) {
console.log(err.message);error TS18046: 'err' is of type 'unknown'.
}jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": [
"*.js"
]
} /**
* @param {string} text
* @throws {RangeError}
*/
export function parseQuantity(text) {
const n = Number(text);
if (!Number.isInteger(n) || n < 1) throw new RangeError(`Bad quantity: ${text}`);
return n;
}
try {
parseQuantity("zero");
} catch (error) {
if (error instanceof RangeError) console.warn(error.message);
else throw error;
}export function parseQuantity(text: string) {
const n = Number(text);
if (!Number.isInteger(n) || n < 1) throw new RangeError(`Bad quantity: ${text}`);
return n;
}
try {
parseQuantity("zero");
} catch (error) {
if (error instanceof RangeError) console.warn(error.message);
else throw error;
}jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} Spelling suggestions in unchecked JavaScript
Editors that use TypeScript's language service now offer spelling suggestions in .js files that are not checked: a misspelled name or property that TypeScript can match to one it knows gets a "Did you mean…?" suggestion. They are suggestions, not errors, and tsc does not print them; full checking still needs checkJs or // @ts-check.
The release notes do not say how to turn them off. This site's tests show it with TypeScript 5.9: the suggestions (TS2570 for a name, TS2568 for a property) appear when checkJs is left out of the config, and "checkJs": false turns them off, together with the JavaScript errors 4.6 added.
Checks stored in a const
Before 4.4, const isText = typeof input === "string" followed by if (isText) did not narrow input. Now it does, as long as the condition is stored in a const and the value it checks is a const, a readonly property or a parameter that is never assigned.
/** @param {string | number} input */
export function normalize(input) {
const isText = typeof input === "string";
if (isText) {
return input.trim();
}
return input.toFixed(2);
}jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": [
"*.js"
]
} A promise used as a condition
TypeScript 4.3 reported a promise stored in a variable and then tested in an if; 4.4 also reports a call tested directly, if (isAdmin()), which is almost always a missing await (TS2801).
/** @returns {Promise<boolean>} */
async function isAdmin() {
return false;
}
export async function greeting() {
if (isAdmin()) {error TS2801: This condition will always return true since this 'Promise<boolean>' is always defined.
return "Welcome back";
}
return (await isAdmin()) ? "Welcome back" : "Hello";
}jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": [
"*.js"
]
} exactOptionalPropertyTypes
A new option, not part of strict, makes an optional property mean "may be missing" rather than "may be undefined". It needs strictNullChecks.
/**
* @typedef {object} User
* @property {string} name
* @property {string} [nickname]
*/
/** @type {User} */
export const user = { name: "Ada", nickname: undefined };error TS2375: Type '{ name: string; nickname: undefined; }' is not assignable to type 'User' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties.
Types of property 'nickname' are incompatible.
Type 'undefined' is not assignable to type 'string'.jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": [],
"exactOptionalPropertyTypes": true
},
"include": [
"*.js"
]
} What did not change
Without strict (or with useUnknownInCatchVariables off), caught errors stay any.
- Class
staticblocks, added to TypeScript in this release, are JavaScript syntax, so they work in.jsfiles as written.
Upgrade notes
From the breaking changes; the first two come with strict:
- TS18046 (
erris of typeunknown) wherever acatchblock reads a property of the error. - TS2801 where a promise is tested without
await. - Abstract properties cannot have initializers (TypeScript syntax only).
Gotchas
- A JSDoc type on the catch variable can only be
anyorunknown(anything else is TS1196), so the fix is a check such aserr instanceof Error, which also holds at run time.
Sources
- TypeScript 4.4 release notes · TypeScript handbook
- Announcing TypeScript 4.4 · TypeScript blog
Last reviewed 20 October 2026.