TypeScript release

TypeScript 4.4 for JavaScript users

  • Released 26 August 2021
  • Examples run with TypeScript 5.9

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

ReleaseTypeScript 4.4
Released26 August 2021, the day of its announcement on the TypeScript blog
SourceTypeScript 4.4 release notes
ExamplesEvery example runs through TypeScript 5.9.3 when this site is built.
Previous releaseTypeScript 4.3
Next releaseTypeScript 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.

main.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: main.js(5,15): error TS18046: 'err' is of type 'unknown'. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. main.js(5,15): error TS18046: 'err' is of type 'unknown'. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. main.js(5,15): error TS18046: 'err' is of type 'unknown'. checked 21 October 2026
try {
  JSON.parse("{");
} catch (err) {
  console.log(err.message);error TS18046: 'err' is of type 'unknown'.
}
useUnknownInCatchVariables on: err is unknown.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": [
    "*.js"
  ]
}
parse.js parse.ts TypeScript 5.9.3 TypeScript 5.9.3, run 21 October 2026: No errors. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. No errors. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. No errors. checked 21 October 2026
/**
 * @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;
}
Narrow the caught error before using it.
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.

normalize.js TypeScript 5.9.3 TypeScript 5.9.3, run 21 October 2026: No errors. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. No errors. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. No errors. checked 21 October 2026
/** @param {string | number} input */
export function normalize(input) {
  const isText = typeof input === "string";
  if (isText) {
    return input.trim();
  }
  return input.toFixed(2);
}
A check stored in a const still narrows the value.
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).

admin.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: admin.js(8,7): error TS2801: This condition will always return true since this 'Promise<boolean>' is always defined. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. admin.js(8,7): error TS2801: This condition will always return true since this 'Promise<boolean>' is always defined. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. admin.js(8,7): error TS2801: This condition will always return true since this 'Promise<boolean>' is always defined. checked 21 October 2026
/** @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";
}
A promise in a condition, called directly.
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.

main.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: main.js(9,14): 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'. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. main.js(9,14): 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'. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. main.js(9,14): 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'. checked 21 October 2026
/**
 * @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'.
exactOptionalPropertyTypes on: an optional property may be missing, not undefined.
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 static blocks, added to TypeScript in this release, are JavaScript syntax, so they work in .js files as written.

Upgrade notes

From the breaking changes; the first two come with strict:

  • TS18046 (err is of type unknown) wherever a catch block 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 any or unknown (anything else is TS1196), so the fix is a check such as err instanceof Error, which also holds at run time.

Sources

Last reviewed 20 October 2026.