← All posts

5 min read

TypeScript 7.1 Beta: stable es2026 lib and what to check

TypeScript 7.1 Beta makes es2026 stable: Map upserts, Uint8Array base64, Error.isError, plus typed import attributes and one breaking change.

TypeScript 7.1 Beta landed on October 20, and the headline for most apps is not the compiler speedup: it is that --target es2026 and --lib es2026 are now stable. This post walks through the new library types, the import features that came with the beta, and the one breaking change you should check before upgrading. I ran every snippet below with typescript@7.1.0-beta and in Chromium.

// tsconfig: "target": "es2026", "lib": ["es2026"]
const cache = new Map<string, string[]>();

function add(key: string, value: string) {
  cache
    .getOrInsertComputed(key, () => [])
    .push(value);
}

add("x", "1");
add("x", "2");
console.log(cache.get("x")); // ["1", "2"]

Trying the beta

The 7.1 Beta announcement says to install it with the beta tag:

npm install -D typescript@beta
npx tsc -v   # Version 7.1.0-beta

The team expects a release candidate about a month after the beta, then a final release shortly after. Treat this as something to try on a branch, not to ship.

What es2026 adds to the lib

According to the announcement, the stable es2026 target and lib include these:

  • Map and WeakMap: getOrInsert() and getOrInsertComputed()
  • Uint8Array: toBase64(), toHex(), fromBase64(), fromHex(), setFromBase64(), setFromHex()
  • Error.isError(), Array.fromAsync(), Math.sumPrecise(), Iterator.concat(), JSON.rawJSON()

These are type declarations only. TypeScript does not polyfill anything, so the type check passing says nothing about your runtime. More on that below.

Map upsert without the get/set dance

The classic "group by key" code does a get, a null check, a set, and then another get. getOrInsertComputed collapses that, and it only calls your factory when the key is missing:

const groups = new Map<string, number[]>();

for (const [k, v] of [["a", 1], ["b", 2], ["a", 3]] as const) {
  groups.getOrInsertComputed(k, () => []).push(v);
}
// Map { "a" => [1, 3], "b" => [2] }

Use getOrInsert(key, value) when the default is a cheap constant. Use the Computed form when building the default is expensive or allocates, such as a new array or object. The MDN page for Map.prototype.getOrInsertComputed has the details.

Base64 and hex on Uint8Array

No more btoa(String.fromCharCode(...bytes)), which also breaks on large arrays because of the argument spread:

const bytes = new Uint8Array([72, 105, 33]);

bytes.toBase64();   // "SGkh"
bytes.toHex();      // "486921"
bytes.toBase64({ alphabet: "base64url", omitPadding: true });

Uint8Array.fromBase64("SGkh"); // Uint8Array [72, 105, 33]
Uint8Array.fromHex("486921");  // Uint8Array [72, 105, 33]

In my Chromium run, the base64 and hex values matched the comments above exactly. The base64url option type-checks, which is handy for JWT-style payloads.

Error.isError for cross-realm errors

instanceof Error fails for errors created in another realm, such as an iframe. Error.isError does not. I checked this in Chromium by creating an error from an iframe's Error constructor:

Error.isError(new TypeError("x")); // true
Error.isError({ message: "x" });   // false

// error created by an iframe's Error constructor:
//   Error.isError(err)   -> true
//   err instanceof Error -> false

This matters for code that handles errors coming from other realms, such as iframes.

The rest

Array.fromAsync(gen()) resolved to [1, 2] for an async generator, Iterator.concat([1, 2], new Set([3])) yielded 1, 2, 3, and Math.sumPrecise([0.1, 0.2, 0.3]) returned 0.6 where plain addition gives 0.6000000000000001.

The runtime is your problem

Here is the part that bites. With lib: ["es2025"], the same file fails with errors like TS2550: Property 'getOrInsertComputed' does not exist on type 'Map<string, string[]>'. Switching to es2026 makes the errors go away, but only the types change.

I checked the actual runtimes:

  • Chromium 153 (Playwright): all of the APIs above were defined.
  • Node 22.23.4: Array.fromAsync and JSON.rawJSON exist, but Error.isError and Uint8Array.fromBase64 are undefined.

So the rule is the same as always: pick your lib to match the oldest runtime you support, or polyfill. Check MDN's compatibility tables for each API before you flip the setting in a shared config.

Wildcard module declarations with import attributes

The beta also lets an ambient module declaration require a specific import attribute. That gives you typed imports for things like CSS, text, or bytes without a global declare module "*.md" that matches by file extension:

// text.d.ts
declare module "*" with { type: "text" } {
  const content: string;
  export default content;
}
import notes from "./notes.md" with { type: "text" };

const len: number = notes.length; // ok
const n: number = notes;          // error TS2322: string is not assignable to number

I confirmed both lines: the first compiles, the second produces TS2322. Per the announcement, equivalent forms exist for type: "css" (a CSSStyleSheet) and type: "bytes" (a Uint8Array), and matching imports are not checked against disk. Declare these in your app, not in a published library.

The beta also adds source phase imports (import source mod from "./x.wasm"), which need --module set to esnext, nodenext or preserve. I did not test those, so check the announcement before relying on them.

Breaking change: distributive conditional types

The announcement calls out stricter handling inside distributed branches of conditional types. The type parameter there is now treated as a narrower, separate type parameter. Code that passed it where a constraint on the original parameter was required can now error. If your type-level utilities start failing after the upgrade, this is the first place to look. The suggested fixes are to remove an unnecessary constraint or to add extra conditional checks.

The generated DOM types also changed, so expect some noise in DOM-heavy code (tracked in microsoft/TypeScript#63702).

A config reminder from 6.0

If you are coming from older setups, the 6.0 line changed defaults you will hit in CI. Per the TypeScript 6.0 announcement, running tsc foo.ts while a tsconfig.json exists is now an error (TS5112) unless you pass --ignoreConfig. I hit exactly that error while testing, so update any scripts that compile single files.

Takeaway

  • Try typescript@beta on a branch: es2026 is stable, and it brings Map upserts, Uint8Array base64/hex, Error.isError and more as types.
  • Types are not polyfills. Node 22.23.4 lacked Error.isError and Uint8Array.fromBase64 in my test, so match lib to your oldest runtime.
  • declare module "*" with { type: "text" } gives you attribute-based typing for non-JS imports.
  • Watch for new errors in conditional-type utilities and DOM-heavy code.
  • Wait for the RC and final release before shipping it to production.