Deno logoDeno

Deno 1.17 Release Notes

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

If you already have Deno installed, you can upgrade to 1.17 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

Import assertions and JSON modules

Deno v1.17 ships with full support for import assertions and JSON modules.

Import assertions is a Stage 3 proposal that shipped in V8 earlier this year. The main use case of this proposal is to allow imports of JSON modules; which without import assertions could pose a security vulnerability.

Prior to this release the only way to read JSON file was to use fetch() or Deno.readTextFile() in combination with JSON.parse(). Starting with this release you can import JSON files directly into your module graph:

// Prior to Deno v1.17

// Get JSON data from remote server
const response = await fetch("");
const jsonData = await response.json();

// Get JSON data from local file
const text = await Deno.readTextFile("./data.json");
const jsonData = JSON.parse(text);

// Starting with Deno v1.17

// Get JSON data from remote server
import jsonData from "" assert { type: "json" };

// Get JSON data from local file
import jsonData from "./data.json" assert { type: "json" };

You can also import JSON modules dynamically using import():

const jsonData = await import("./data.json", { assert: { type: "json" } });

An advantage of using import declarations for JSON modules is that you don't need to provide permissions to import them; dynamic imports are subject to regular permissions checks.

Improvements to the Web Cryptography API

This release once again adds new features to the Web Cryptography API. With these additions, the API is now nearing feature parity with browsers.

Added in v1.17:

  • crypto.subtle.importKey:
    • support importing RSA keys in SPKI format
    • support importing RSA keys in JWK format
    • support importing AES keys in JWK format
    • support importing EC keys in PKCS#8 format
    • support importing EC keys in SPKI format
    • support importing EC keys in JWK format
  • crypto.subtle.exportKey:
    • support exporting RSA keys in JWK format
    • support exporting AES keys in JWK format
  • crypto.subtle.unwrapKey:
    • support for unwrapping using RSA-OAEP
    • support for unwrapping using AES-GCM

The last few features (elliptic curve key imports and exports, and more AES encryption/decryption formats) are still in development, but are coming along nicely and will likely be available in the next release.

See progress here: denoland/deno#11690

Thanks to Yacine Hmito, Sean Michael Wykes and Divy Srivastava for contributing these improvements.

--no-check=remote flag

One of the common challenges with Deno is that sometimes remote dependencies don't type check properly even though they have the correct runtime behavior. When type checking your local code this can cause diagnostics to be issued and your program not to run even when you have no control over the remote dependencies.

The --no-check=remote option was added to help with this. When this flag is passed, the program will be type checked as a whole, but any diagnostics that are coming from a remote module will be discarded. If there are no local diagnostics, your program will run.

Pass the option on the command line:

> deno run --no-check=remote server.ts

We encourage you to experiment with this flag and report feedback. We think this mode might be a good default type checking configuration for future releases of Deno.

Unstable support for negotiating ALPN for Deno.connectTls()

ALPN is a TLS extension that allows a client to negotiate a communication protocol with a server. This is used when using HTTP/2 for example, to negotiate if the server supports HTTP/2.

Up to now Deno did not support manually negotiating ALPN. This release adds that capability. If you are interested in this functionality, we'd love to hear your feedback.

An example:

const conn = await Deno.connectTls({
  hostname: "",
  port: 443,
  alpnProtocols: ["h2", "http/1.1"],
const { alpnProtocol } = await conn.handshake();
if (alpnProtocol !== null) {
  console.log("Negotiated protocol:", alpnProtocol);
} else {
  console.log("No protocol negotiated");

Thanks to Yury Selivanov for contributing this feature.

Unref timers

This release adds two new unstable APIs:

  • Deno.unrefTimer(id: number)
  • Deno.refTimer(id: number)

These APIs can be used to change the behavior of timers (setTimeout and setInterval) to block or not block the event loop from exiting.

By default all timers block the event loop from exiting until they are cleared.

In the following example the program will finish after printing hello from timeout after 5 seconds.

setTimeout(() => {
  console.log("hello from timeout");
}, 5000);

console.log("hello world!");

However sometimes it's not desired for timers to prevent the event loop from exiting. An example of this behavior is periodic collection of telemetry data:

// Collect data every 5s
setInterval(() => {
  const data = collectSomeData();
  fetch("", { method: "POST", body: data })
    .catch(() => {});
}, 5000);

// Main entry point to the program
await longRunningTask();

In the above example, we want our program to exit once longRunningTask finishes. To achieve this behavior we can "unref" the interval:

// Collect data every 5s
const intervalId = setInterval(() => {
  const data = collectSomeData();
  fetch("", { body: data }).catch(() => {});
}, 5000);
// Unref the telemetry interval so the program exits immediately, once
// `longRunningTask` finishes.

// Main entry point to the program
await longRunningTask();

Updates to abort reasons in AbortSignal

The last release added support for aborting AbortSignals with specific abort reasons. These reasons are now correctly propagated through these APIs:

  • Deno.readFile() and Deno.readTextFile()
  • Deno.writeFile() and Deno.writeTextFile()
  • All of the WHATWG streams APIs (ReadableStream, TransformStream, WritableStream, and friends).
  • WebSocketStream
  • fetch, Request, and Response

In addition we now support the new throwIfAborted() method on AbortSignal that can be used to synchronously throw an error if the signal has already been aborted.

const controller = new AbortController();
const signal = controller.signal;

try {
} catch (err) {
  unreachable(); // the abort signal is not yet aborted

controller.abort("Hello World");

try {
  unreachable(); // the method throws this time
} catch (err) {
  assertEquals(err, "Hello World");

Thanks to Andreu Botella for contributing these changes.

Updates to the Deno Language Server

The Deno Language Server adds support for a revised module registry suggestion protocol. This will allow large package registries (like NPM) to provide partial result sets, intelligent incremental search, and documentation details about packages and modules as part of the editing experience. Adding the new capabilities to and will happen soon after 1.17 ships.

Another behind the scenes improvement is that the values from the package registries will respect the cache headers supplied. Previously, the results from a registry were cached indefinitely and required users to clear the cache manually to get new or changed results.

To take advantage of these features, ensure that your editor's Deno configuration includes supported registries in the deno.suggest.imports.hosts are configured. For example:

  "deno.suggest.imports.hosts": {
    "": true,
    "": true,
    "": true

While not related directly to the Deno 1.17 release, vscode_deno is being updated to enable known package registries which support the registry protocol by default. This will increase visibility of this feature, making it easier for people to find code to run under Deno.

With the new features of the registry protocol, we hope to work with the likes of, Skypack and JSPM to provide intelligent registries to make it easy to consume code from the wider JavaScript and TypeScript ecosystem.

If you want to add support for import completions to your own registry, you can read more about the registry protocol here.

Also, the Deno Language Server reaches a level of maturity, adding a couple of outstanding features, including goto type definition support and workspace symbol searching.

Updates to deno test

Deno ships with a built-in test runner. You can register tests using Deno.test() API. Until now there were two available overloads of the API - "shorthand" and "definition" based. The shorthand works well for simple tests, while the definition gives the most control, allowing for example tests to be ignored:

import { assertEquals } from "";

Deno.test("My test description", (): void => {
  assertEquals("hello", "hello");

  name: "example test",
  ignore: == "windows",
  fn(): void {
    assertEquals("world", "world");

It becomes quite cumbersome if you need to ignore/focus a single test that was written using "shorthand" overload. You would need to rewrite whole test to use the "definition" overload. To alleviate this problem and provide more flexibility in writing tests we've added four more overloads to the Deno.test() API.

import { assertEquals } from "";

Deno.test(function myTestName(): void {
  assertEquals("hello", "hello");

Deno.test("My test description", { permissions: { read: true } }, (): void => {
  assertEquals("hello", "hello");

  { name: "My test description", permissions: { read: true } },
  (): void => {
    assertEquals("hello", "hello");

Deno.test({ permissions: { read: true } }, function myTestName(): void {
  assertEquals("hello", "hello");

You can now mix both forms, and provide a partial "definition" as the first or second argument.

Another update is for users using unstable test steps API. The final test report output will now include number of test steps with distinction for passed/ignored/failed steps. The test step API is scheduled to be stabilized in v1.18

Deno.test("nested failure", async (t) => {
  const success = await t.step("step 1", async (t) => {
    let success = await t.step("inner 1", () => {
      throw new Error("Failed.");
    if (success) throw new Error("Expected failure");

    success = await t.step("inner 2", () => {});
    if (!success) throw new Error("Expected success");

  if (success) throw new Error("Expected failure");
$ deno test --unstable test.ts
running 1 test from file:///dev/test.ts
test nested failure ...
  test step 1 ...
    test inner 1 ... FAILED (11ms)
      Error: Failed.
          at file:///dev/test.ts:4:13
          at testStepSanitizer (deno:runtime/js/40_testing.js:187:13)
          at asyncOpSanitizer (deno:runtime/js/40_testing.js:68:15)
          at resourceSanitizer (deno:runtime/js/40_testing.js:138:13)
          at exitSanitizer (deno:runtime/js/40_testing.js:170:15)
          at TestContext.step (deno:runtime/js/40_testing.js:800:19)
          at file:///dev/test.ts:3:27
          at testStepSanitizer (deno:runtime/js/40_testing.js:187:13)
          at asyncOpSanitizer (deno:runtime/js/40_testing.js:68:15)
          at resourceSanitizer (deno:runtime/js/40_testing.js:138:13)
    test inner 2 ... ok (11ms)
  FAILED (32ms)
FAILED (44ms)


nested failure
Error: 1 test step failed.
    at runTest (deno:runtime/js/40_testing.js:432:11)
    at async Object.runTests (deno:runtime/js/40_testing.js:541:22)


    nested failure

test result: FAILED. 0 passed (1 step); 1 failed (2 steps); 0 ignored; 0 measured; 0 filtered out (66ms)

Updates to the file watcher

Deno ships with a built-in file watcher that can be activated using the --watch flag.

The file watcher automatically discovers files to watch based on the files contained in module graph (for the deno run subcommand) or file paths passed as arguments (for subcommands like deno lint or deno fmt).

In this release the --watch flag for deno run accepts an optional list of external files that should also be watched for changes.

$ deno run --watch=data/external.txt script.ts

Thanks to Jasper van den Ende for contributing this feature.

A small quality of life improvement to the watcher is automatic clearing of terminal screen on restart:

Updates to the REPL

Module specifier completions

The REPL (deno repl) now provides completions for relative specifiers on the file system and absolute specifiers from the registry.

These completions can be requested by pressing the <TAB> key while typing a module specifier.

Node.js compatibility mode

The REPL can now be run in Node.js compatibility mode; allowing access to Node's global variables (eg. process), import CommonJS modules with require() and providing ESM resolution compatible with Node.js. To enter REPL in compatibility mode use deno repl --compat --unstable.

$ deno repl --compat --unstable
Deno 1.17.0
exit using ctrl+d or close()
> console.log(process)
process {
  _events: {},
  _eventsCount: 0,
  _maxListeners: undefined,

Ignore certificate errors

This release adds the --unsafely-ignore-certificate-errors flag to the deno repl subcommand that allows disabling TLS certificate verification.

Note that this is a dangerous setting. You should not use this flag to silence certificate errors. Disabling TLS certificate verification (ignoring certificate errors) makes TLS pointless because it allows MITM attacks. Data sent over a TLS connection is not confidential if the certificate has not been verified and trusted.

Read more in Deno 1.13 Release Notes blog post.

Thanks to VishnuJin for contributing this feature.

Updates to the FFI API

Following improvements to the FFI API in v1.15 this release adds two new APIs for working with pointers across languages boundary: Deno.UnsafePointer and Deno.UnsafePointerView. Additionally buffer parameter type was renamed to pointer.


static BUFFER: [u8; 8] = [1, 2, 3, 4, 5, 6, 7, 8];

pub extern "C" fn return_buffer() -> *const u8 {

pub extern "C" fn is_null_ptr(ptr: *const u8) -> u8 {
  ptr.is_null() as u8
// test_ffi.js
const [libPrefix, libSuffix] = {
  darwin: ["lib", "dylib"],
  linux: ["lib", "so"],
  windows: ["", "dll"],
const libPath = `${targetDir}/${libPrefix}test_ffi.${libSuffix}`;

const dylib = Deno.dlopen(libPath, {
  "return_buffer": { parameters: [], result: "pointer" },
  "is_null_ptr": { parameters: ["pointer"], result: "u8" },

const ptr = dylib.symbols.return_buffer();
dylib.symbols.print_buffer(ptr, 8);
const ptrView = new Deno.UnsafePointerView(ptr);
const into = new Uint8Array(6);
const into2 = new Uint8Array(3);
const into2ptr = Deno.UnsafePointer.of(into2);
const into2ptrView = new Deno.UnsafePointerView(into2ptr);
const into3 = new Uint8Array(3);
ptrView.copyInto(into2, 3);
const string = new Uint8Array([ TextEncoder().encode("Hello from pointer!"),
const stringPtr = Deno.UnsafePointer.of(string);
const stringPtrview = new Deno.UnsafePointerView(stringPtr);
$ deno run --allow-ffi --allow-read --unstable test_ffi.js
[1, 2, 3, 4, 5, 6, 7, 8]
[ 1, 2, 3, 4, 5, 6 ]
[ 4, 5, 6 ]
[ 4, 5, 6 ]
Hello from pointer!

We are already seeing interesting projects using these new APIs, eg. native bindings to SQLite C API:

FFI requires the --allow-ffi and the --unstable flags. Beware that enabling FFI will effectively disable all security protections in Deno.

Thank you to Elias Sjögreen for contributing this feature.

TypeScript 4.5

Deno 1.17 ships with the latest stable version of TypeScript. This was part of what unlocked the ability to support import assertions. Also, a new way to import types is supported:

Previously it was recommended to import items only be used in the type position this way:

import { A } from "";
import type { B } from "";

But now these can be combined into a single import statement:

import { A, type B } from "";

Also, TypeScript 4.5 added support for .mts and .d.mts extensions as well as first class support for .mjs files, which are properly handled in Deno 1.17.

For more information on new features in TypeScript see TypeScript's 4.5 blog post

Updates to the standard library

This release includes a number of new features and breaking changes to the standard library. We are hard at work at cleaning up the standard library to get it into a state where it can stabilized.

Breaking changes

In version 0.118.0 of deno_std following APIs undergone breaking changes:

  • ws module was removed - instead use Deno.upgradeWebSocket() API
  • assertThrowsAsync from testing/asserts.ts was removed - instead use assertRejects API from the same module
  • http/server_legacy.ts was removed - a legacy implementation of HTTP server, instead use http/server.ts
  • copy API was removed from fs/mod.ts - instead import this API directly from fs/copy.ts
  • onSignal was removed from signals module - instead use Deno.addSignalListener() API
  • findLast and findLastIndex were removed from collections module
  • http server module no longer accepts string address, use { host: string, port: number } interface instead

New HTTP server options

Previously the serve function in the std/http module would swallow all errors produced by the HTTP handler function. This release changes that behavior, so that the server will log all errors produced by handlers using console.error. You can customize the behavior by passing a custom onError function to the serve options.

import { Server } from "";

const port = 4505;
const handler = (request: Request) => {
  const body = `Your user-agent is:\n\n${
    ) ?? "Unknown"

  return new Response(body, { status: 200 });

const onError = (_error: unknown) => {
  return new Response("custom error page", { status: 500 });

const server = new Server({ port, handler, onError });

Additions to std/collections

std/collections is a playground for us to experiment with more advanced functions for iterating over arrays and iterables. This release adds aggregateGroups. It applies the given aggregator to each group in the given grouping, returning the results together with the respective group keys:

import { aggregateGroups } from "";
import { assertEquals } from "";
const foodProperties = {
  "Curry": ["spicy", "vegan"],
  "Omelette": ["creamy", "vegetarian"],
const descriptions = aggregateGroups(
  (current, key, first, acc) => {
    if (first) {
      return `${key} is ${current}`;
    return `${acc} and ${current}`;
assertEquals(descriptions, {
  "Curry": "Curry is spicy and vegan",
  "Omelette": "Omelette is creamy and vegetarian",

Byte formatting using std/fmt/bytes

A new std/fmt/bytes module has been added to the standard library. It provides a function for formatting bytes in a human readable format:

import { prettyBytes } from "";
import { assertEquals } from "";

assertEquals(prettyBytes(1024), "1.02 kB");
assertEquals(prettyBytes(1024, { binary: true }), "1 kiB");

This API is a port of the popular pretty-bytes npm package.