Deno logoDeno

Deno 1.16 Release Notes

Luca Casonato, David Sherret, Kitson Kelly

Deno 1.16 has been tagged and released with the following features and changes:

If you already have Deno installed, you can upgrade to 1.16 by running

deno upgrade

If you are installing Deno for the first time, you can use one of the methods listed below:

# Using Shell (macOS and Linux):
curl -fsSL | sh

# Using PowerShell (Windows):
iwr -useb | iex

# Using Homebrew (macOS):
brew install deno

# Using Scoop (Windows):
scoop install deno

# Using Chocolatey (Windows):
choco install deno

New features and changes

fetch now supports fetching file URLs

This release adds support for using fetch to read files from the local file system. This is useful for fetching files relative to the current module using import.meta.url, like is often done for WASM modules.

Here is an example of reading the /etc/hosts file:

const resp = await fetch("file:///etc/hosts");
const text = await resp.text();

Note: the above example will not work on Windows, as the /etc/hosts file is located in a different location on Windows. Try the code below instead:

const resp = await fetch("file:///C:/Windows/System32/drivers/etc/hosts");

The --allow-read permission is required to use fetch to read files from the local file system.

The contents of the file are read in chunks, so this is a great way to stream a large file via HTTP without having to load the entire file into memory:

import { serve } from "";

serve((_req) => {
  const resp = await fetch(new URL("./static/index.html", import.meta.url));
  return new Response(resp.body, {
    headers: { "content-type": "text/html; charset=utf-8" },

The behaviour of file fetches is not specified in any web specification, but our implementation is based on the implementation for reading files from disk in Firefox:

  • If a file is not found, the promise returned from fetch rejects with a TypeError.
  • If the item at the specified path is a directory, the promise returned from fetch rejects with a TypeError.
  • No content-length header is set on the response. This is because the response body is a stream, so the exact length cannot be known ahead of time.
  • No content-type header is set on the response. If you want to derive a content type from the file extension, you can use the media_types module on

Support for new JSX transforms

React 17 introduced a new JSX transform which makes improvements to the JSX transform API as well as allowing automatic importing of the JSX runtime library. Starting with this release, Deno supports these transforms.

It can be used two different ways. The first way is using the @jsxImportSource pragma in a .jsx or .tsx file. For example, to use Preact from

/** @jsxImportSource */

export Welcome({ name }) {
  return (
      <h1>Welcome {name}</h1>

The other option is using a configuration file and setting the compiler options project wide:

  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": ""

And then passing the --config file on the command line (or setting the deno.config option in your editor).

For more details, refer to the manual section on configuring JSX.

New unstable signal listener API

This release adds a new unstable API for listening to operating system signals. The new API is currently experimental and may change in the future. It supersedes the existing Deno.signals API (which was also unstable).

const listener = () => {

// Starts listening to SIGTERM
Deno.addSignalListener("SIGTERM", listener);

// Stops listening to SIGTERM
Deno.removeSignalListener("SIGTERM", listener);

We are interested in community feedback. If you have any suggestions, please open an issue.

Error.cause is now displayed in the console

Since Deno 1.13, the Error.cause property has been supported as a way to attach a cause to an error. This is useful for debugging errors that occur deep inside of an application, allowing developers to wrap these errors in useful information to help debug issues.

Starting in this release we will display the Error.cause property in the console when an error is thrown or logged via console.log:

> throw new Error("main error", { cause: new TypeError("caused by this") })
Uncaught Error: main error
    at <anonymous>:2:7
Caused by TypeError: caused by this
    at <anonymous>:3:12

This matches the behaviour of Node.js 17.

Thanks to Kenta Moriuchi for implementing this feature.

Handshaking TLS connections can now be done explicitly

When establishing a connection over TLS before data can be read or written on the encrypted connection, a TLS handshake needs to be performed. Most users do not need to care about the specifics of handshaking, which is why we do it automatically when a user first tries to read or write data on the connection.

This release adds a handshake() method to Deno.TlsConn that can be called to explicitly trigger a handshake. This method returns a promise that resolves once the handshake is complete. If the method is called after the handshake is already complete, the promise will resolve immediately. If the method is called while a handshake is in progress, but not yet complete, the returned promise will resolve once the handshake is completed.

Improvements to Web Streams API

This release introduces multiple new features from the Web Streams API:

  • ReadableStreamBYOBReader is now supported. These "bring-your-own-buffer" (also known as BYOB) ReadableStream readers allow reading into a developer-supplied buffer, thus minimizing copies in comparison to the regular ReadableStreamDefaultReader. You can read more about this API on MDN.
  • WritableStreamDefaultController.signal is now supported. See the explainer for more information.
  • ReadableStream.getIterator has been removed. This method has been deprecated since Deno 1.7, and was never implemented in any browser. The ReadableStream itself has always implemented the AsyncIterable protocol, so use that instead when iterating over a stream.

Thanks to @crowlKats for the work on the Web Streams implementation.

Deno.startTls stabilized

In Deno there are two ways to connect to a server via TLS: Deno.connectTls and Deno.startTls. The first is used when you want open a TCP connection and then immediately start talking TLS over it. The latter is used when you need to create a plain text TCP connection first, exchange some data, and then start talking TLS over it.

Deno 1.16 stabilizes the Deno.startTls API. This makes it possible to write an SMTP driver for stable Deno. It also allows Postgres and MySQL drivers written for stable Deno. The driver for Deno now works fully in Deno stable:

import { Client } from "";

const client = new Client({
  user: "user",
  database: "test",
  hostname: "",
  port: 5432,
  tls: {
    enforce: true,
    caCertificates: [await Deno.readTextFile("/path/to/ca.crt")],
await client.connect();

const result = await client.queryObject("SELECT id, name FROM people");
console.log(result.rows); // [{id: 1, name: 'Carlos'},  ...]

Per-test permissions are now stable

Back in Deno 1.10 we introduced a feature that allows you to set per test permissions. This makes it super easy to test how your program behaves with different permissions set. This feature is now stable: it does not require --unstable anymore. For an example, check out the 1.10 blog post linked above.

Keep in mind, permissions requested by a test case cannot exceed permissions granted to the process using --allow-* flags. If a key is omitted in permissions object then it inherits its value from the respective --allow-* flag.

localStorage does not require --location anymore

In previous versions of Deno, the localStorage API could only be used if the user started a Deno process with a --location flag. This release adds an implicit opaque key for the storage bucket used by localStorage when you start Deno without a --location flag. This is derived as follows:

  • When using the --location flag, the origin for the location is used to uniquely store the data. That means a location of and and would all share the same storage, but would be different.
  • If there is no location specifier, but there is a --config configuration file specified, the absolute path to that configuration file is used. That means deno run --config deno.jsonc a.ts and deno run --config deno.jsonc b.ts would share the same storage, but deno run --config tsconfig.json a.ts would be different.
  • If there is no configuration or location specifier, Deno uses the absolute path to the main module to determine what storage is shared. The Deno REPL generates a "synthetic" main module that is based off the current working directory where deno is started from. This means that multiple invocations of the REPL from the same path will share the persisted localStorage data.

Some examples to help clarify this behaviour:

# You can persist data, even without --location
$ cat one.js
localStorage.setItem("file", Deno.mainModule);
$ deno run one.js
$ deno run one.js

# The key for the storage bucket is derived from the main module, so a module
# cannot read or write data written by a program with a different main module.
$ cat two.js
localStorage.setItem("file", Deno.mainModule);
$ deno run two.js
$ deno run two.js

# The key is derived from the **main module** (the entrypoint), not the module
# that called `localStorage`!
$ cat three.js
import "./two.js";
$ deno run three.js
$ deno run three.js

# If you have a config file, that is used as the key for the storage bucket.
$ deno run --config=deno.jsonc one.js
$ deno run --config=deno.jsonc one.js
$ deno run --config=deno.jsonc two.js
$ deno run --config=deno.jsonc one.js

# You can use --location to specify an explicit origin though, like before. In
# this case, different main modules (entrypoints) can share a single storage
# bucket.
$ deno run --location= one.js
$ deno run --location= one.js
$ deno run --location= two.js
$ deno run --location= one.js

More information is available in the manual:

Support for specifying a reason when aborting an AbortSignal

WHATWG has recently specified support for specifying a reason when aborting an AbortSignal. Deno is the first platform to implement this new feature:

const abortController = new AbortController();
console.log(abortController.signal.reason); // DOMException: The signal has been aborted

const abortController = new AbortController();
const reason = new DOMException("The request timed out", "TimeoutError");
console.log(abortController.signal.reason); // DOMException: The request timed out

Thanks to @crowlKats for implementing this feature.

Deno to npm package build tool

We continue to make improvements to our Node compatibility mode (--compat) but nothing concrete to announce in this release. However, we have released a new system called dnt for publishing modules written in Deno as npm packages.

By default, dnt will transform your Deno module to canonical TypeScript, type check, build an ESM & CommonJS hybrid package with TypeScript declaration files, and finally run your Deno test code in Node.js against the output. Once done, you only have to npm publish the output directory.

An example project that is already using dnt is deno_license_checker which is now published as an npm package:

Try it out with:

npm install -g @kt3k/license-checker

This allows you to use Deno-first code in Node environments.

It's still early days, but we would appreciate if you try it out, thoroughly test and inspect its output, and see what problems or challenges arise so we can prioritize and fix them.

V8 updated to version 9.7

This release ships with V8 9.7. Deno 1.15 shipped with V8 9.5, so this time you are getting two V8 versions worth of new JS goodies 😀

As usual, the V8 release also brings with it a bunch of performance improvements and bug fixes.

See V8's release notes for more details:

WebAssembly Reference Types now supported

The Reference Types proposal, allows using external references from JavaScript opaquely in WebAssembly modules. The externref (formerly known as anyref) data type provides a secure way of holding a reference to a JavaScript object and is fully integrated with V8's garbage collector.

This feature was introduce in V8 9.6. See

findLast and findLastIndex array methods

The findLast and findLastIndex methods on Arrays and TypedArrays find elements that match a predicate from the end of an array.

For example:

[1, 2, 3, 4].findLast((el) => el % 2 === 0);
// → 4 (last even element)

For more info, see the feature explainer.