TypeScript release

TypeScript 4.1 for JavaScript users

  • Released 19 November 2020
  • Examples run with TypeScript 5.9

TypeScript 4.1 was announced on 19 November 2020. For JavaScript projects its most visible change is a small one: checkJs now implies allowJs, so a tsconfig.json needs only one of them. (A jsconfig.json already defaulted allowJs to true.)

It also added noUncheckedIndexedAccess, template literal types and key remapping, editor support for @see, and a breaking change that reaches most JavaScript code that creates promises: resolve() now needs a value unless the promise is a Promise<void>.

At a glance

ReleaseTypeScript 4.1
Released19 November 2020, the day of its announcement on the TypeScript blog
SourceTypeScript 4.1 release notes
ExamplesEvery example runs through TypeScript 5.9.3 when this site is built.
Previous releaseTypeScript 4.0
Next releaseTypeScript 4.2

What changed for JavaScript

checkJs implies allowJs

Setting "checkJs": true alone now brings .js files into the program and checks them. This site's tests check the rule against TypeScript 5.9; the checkJs page has the details.

profile.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: profile.js(5,15): error TS2551: Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'? 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. profile.js(5,15): error TS2551: Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'? 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. profile.js(5,15): error TS2551: Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'? checked 21 October 2026
const user = { name: "Ada", role: "admin" };

let label = user.name;
label = label.toUppercase();error TS2551: Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?

export { label };
checkJs on, no annotations at all.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}

resolve() needs a value

The resolve function of new Promise takes a value that is no longer optional, so resolve() with nothing is an error unless the promise is typed Promise<void>. In a .js file the message says so (TS2810: new Promise() needs a JSDoc hint). The simplest hint is @returns {Promise<void>} on the function that returns the promise.

wait.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: wait.js(5,22): error TS2810: Expected 1 argument, but got 0. 'new Promise()' needs a JSDoc hint to produce a 'resolve' that can be called without arguments. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. wait.js(5,22): error TS2810: Expected 1 argument, but got 0. 'new Promise()' needs a JSDoc hint to produce a 'resolve' that can be called without arguments. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. wait.js(5,22): error TS2810: Expected 1 argument, but got 0. 'new Promise()' needs a JSDoc hint to produce a 'resolve' that can be called without arguments. checked 21 October 2026
/** @param {number} ms */
export function waitLoose(ms) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(), ms);error TS2810: Expected 1 argument, but got 0. 'new Promise()' needs a JSDoc hint to produce a 'resolve' that can be called without arguments.
  });
}

/**
 * @param {number} ms
 * @returns {Promise<void>}
 */
export function wait(ms) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(), ms);
  });
}
resolve() with no value needs a Promise<void>.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": [
    "*.js"
  ]
}

noUncheckedIndexedAccess

A new option, outside strict, makes a lookup by index or key include undefined, so items[0] has to be checked before use.

main.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: main.js(4,13): error TS18048: 'third' is possibly 'undefined'. main.js(9,13): error TS18048: 'stock.cake' is possibly 'undefined'. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. main.js(4,13): error TS18048: 'third' is possibly 'undefined'. main.js(9,13): error TS18048: 'stock.cake' is possibly 'undefined'. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. main.js(4,13): error TS18048: 'third' is possibly 'undefined'. main.js(9,13): error TS18048: 'stock.cake' is possibly 'undefined'. checked 21 October 2026
const names = ["Ada", "Grace"];
const third = names[2];
console.log(third.toUpperCase());error TS18048: 'third' is possibly 'undefined'.

/** @type {Record<string, number>} */
const stock = { tea: 12 };
console.log(stock.cake.toFixed(0));error TS18048: 'stock.cake' is possibly 'undefined'.

for (const name of names) console.log(name.toUpperCase());
noUncheckedIndexedAccess on: an index may point past the end.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": [],
    "noUncheckedIndexedAccess": true
  },
  "include": [
    "*.js"
  ]
}

Template literal types and key remapping

Type syntax gained template literal types (`on${Capitalize<E>}`) and an as clause in mapped types. Both work inside JSDoc braces exactly as in a .ts file.

getters.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
/**
 * @template T
 * @typedef {{ [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K] }} Getters
 */

/** @type {Getters<{ sku: string, price: number }>} */
const product = {
  getSku: () => "TEE-01",
  getPrice: () => 19.5,
};

product.getPrice().toFixed(2);

export { product };
Renaming keys with as and a template literal.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}

any and unknown in && expressions

In value && expr, an unknown (or any) value now makes the whole expression unknown (or any), because the falsy value may be what comes out. A function typed @returns {boolean} that returns such an expression fails; !!value && … fixes it.

guards.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: guards.js(7,3): error TS2322: Type 'unknown' is not assignable to type 'boolean'. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. guards.js(7,3): error TS2322: Type 'unknown' is not assignable to type 'boolean'. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. guards.js(7,3): error TS2322: Type 'unknown' is not assignable to type 'boolean'. checked 21 October 2026
/**
 * @param {unknown} value
 * @returns {boolean}
 */
export function isRecordLoose(value) {
  return value && typeof value === "object";error TS2322: Type 'unknown' is not assignable to type 'boolean'.
}

/**
 * @param {unknown} value
 * @returns {boolean}
 */
export function isRecord(value) {
  return !!value && typeof value === "object";
}
value && … keeps unknown when value is unknown.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": [
    "*.js"
  ]
}

paths without baseUrl

paths no longer needs baseUrl: without it, each path is resolved from the folder of the config file. Older jsconfig.json files often set baseUrl only for this, and TypeScript 6.0 deprecates baseUrl.

src/main.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: src/main.js(4,25): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. src/main.js(4,25): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. src/main.js(4,25): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'. checked 21 October 2026
import { formatPrice } from "@lib/format.js";

console.log(formatPrice("19.99"));error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
The same alias mapped with paths: resolved and checked.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": [],
    "paths": {
      "@lib/*": [
        "./src/lib/*"
      ]
    }
  },
  "include": [
    "src/**/*.js"
  ]
}

What did not change

A jsconfig.json checked the same files before and after: it already defaulted allowJs on.

  • @see was already read as documentation; 4.1 adds go to definition on the names it mentions.

Upgrade notes

New errors in checked JavaScript usually come from:

  • resolve() without a value in new Promise (TS2810 in .js files).
  • x && … with an unknown or any x in boolean-returning functions.
  • Conditional spreads that now produce optional properties.

Gotchas

  • Passing resolve itself, as in setTimeout(resolve, ms), raises no error: the timer calls it with no arguments at run time, but TypeScript checks only direct calls such as resolve().

Sources

Last reviewed 20 October 2026.