Skip to main content

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

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

No type-checking by default

Deno has always run a type-checking pass when asked to execute a program. Evaluation and type-checking however are completely different operations, involving completely different compilers, each with completely different execution speeds. Evaluating code uses Google’s V8 while Type-Checking uses Microsoft’s TypeScript Compiler. Type-checking is quite slow - often taking many seconds to complete. V8 startup and evaluation on the other hand are very fast.

Most programmers these days interact with the type-checker through their editor via the LSP. This means they’re getting hints and completion continuously while they program. Inserting, by default, a full other type-check pass into the deno run operation does not make sense. Type-checking is much more of a lint-like operation - something you do to during CI to make sure there are no type errors.

It should also be highlighted that type-stripping, unlike type-checking, is a fast operation (linear time in the size of the source code). Deno will be type-stripping by default rather than type-checking.

If you still want the previous behavior (type-check then execute) use the --check flag.

We have been planning this change for months, making sure to give users warning and introducing the --check flag. See the notes in v1.22.

This change affects deno run, deno eval, and deno cache. The following table describes the type-checking behavior of various subcommands. Here “Local” means that only errors from local code will induce type-errors, modules imported from https URLs (remote) may have type errors that are not reported. (To turn on type-checking for all modules, use --check=all.)

Subcommand Type checking mode
deno bench 📁 Local
deno bundle 📁 Local
deno cache ❌ None
deno check 📁 Local
deno compile 📁 Local
deno eval ❌ None
deno repl ❌ None
deno run ❌ None
deno test 📁 Local

Remove unstable Deno.sleepSync API

In this release, Deno.sleepSync has been removed because there was no clear necessity for it since this functionality is already available via existing Web APIs. Furthermore, it’s a function that is likely to cause problems: Deno.sleepSync completely blocked the event loop. For example, if it was called in a web server handler function, the server will stop serving requests until it returned.

If you really want this functionality, you can implement it by using the following function:

function sleepSync(timeout) {
  const sab = new SharedArrayBuffer(1024);
  const int32 = new Int32Array(sab);
  Atomics.wait(int32, 0, 0, timeout);

File watcher watches dynamic imports

Starting with v1.23 the built-in file watcher (that you can activate using --watch flag) will also watch for changes in files that were dynamically imported.

// mod.ts <--- this file was being watched
import foo from "./foo.ts"; // <--- this file was also being watched


const bar = await import("./bar.ts"); // <--- this file was not being watched in previous versions;
// starting with v1.23 any changes to `bar.ts` will also cause the process to restart

This feature has allowed for a much better developer experience in the Fresh web framework.

Updates to deno task

Deno includes an unstable deno task sub command that provides a cross platform way to define and execute custom commands specific to a codebase. This release includes several improvements.

New --cwd <path> flag

deno task has a property where by default it executes commands with the directory of the Deno configuration file (ex. deno.json) as the current working directory. This allows tasks to use relative paths and continue to work regardless of where in the descendant directory tree you happen to execute the deno task from.

Although this behavior is mostly desired, there are scenarios where it’s not. In those situations, you may now provide a --cwd <path> flag.

For example, given a task called wasmbuild in a deno.json file:

# use the sub directory project1 as the cwd for the task
deno task --cwd project1 wasmbuild
# use the cwd (project2) as the cwd for the task
cd project2 && deno task --cwd . wasmbuild

Redirects in task definitions

Some redirect support has been added to provide a way to pipe stdout and/or stderr to a file.

For example, the following redirects stdout of deno run main.ts to a file called file.txt on the file system when defined in a deno task and running deno task collect:

  "tasks": {
    "collect": "deno run main.ts > file.txt"

To instead redirect stderr, use 2> instead of >:

deno run main.ts 2> file.txt

To redirect both stdout and stderr, use &>:

deno run main.ts &> file.txt

To append to a file, instead of overwriting an existing one, use two right angle brackets instead of one:

deno run main.ts >> file.txt

Suppressing either stdout, stderr, or both of a command is possible by redirecting to /dev/null. This works in a cross platform way including on Windows.

# suppress stdout
deno run main.ts > /dev/null
# suppress stderr
deno run main.ts 2> /dev/null
# suppress both stdout and stderr
deno run main.ts &> /dev/null

Note that multiple redirects were not implemented and are currently not supported.

Cross platform cat and xargs

deno task features several built in cross platform commands to help reduce verbosity. This update adds a basic implementation of cat and xargs that works on Linux, Mac, and Windows.

Note that not every flag is implemented and please report any issues to the deno_task_shell repo. Also remember that you can always run the native system commands in a non-cross platform way by running it through sh on Linux and Mac (sh -c <command>).

Updates to deno fmt

deno fmt now formats .cjs, .cts, .mjs, and .mts files by default.

Additionally, some unnecessary parenthesis in types will be automatically removed. For example…

type Test = (string | number);

…will now format as…

type Test = string | number;

New unstable Deno.getGid() API

In v1.23 we’ve added a new unstable API: Deno.getGid().

Using this API you can retrieve the ID of the user group. Note that this API works on Linux and macOS, but will return null on Windows.

// 20

This API requires the --allow-env permission flag.

Thank you to James Bradlee for contributing this feature.

deno info supports --config and --no-config flags

This release adds support for --config and --no-config flags. In previous releases, deno info automatically looked up deno.json files, but there was no way to manually specify the config file or disable it entirely.

Thank you to Mark Ladyshau for contributing this feature.

Force a new line in the REPL

This release brings a small quality-of-life improvement to the REPL.

Using ctrl + s combination you can now force a new line when editing code in the REPL.

Thank you to @sigmaSd for contributing this feature.

Updates to FFI API

Previously when functions called via FFI returned 64-bit numbers, normal JavaScript numbers were returned. However, JavaScript numbers only have 53 bits of integer precision—meaning you’d get incorrect results at times. With this change a BigInt is returned instead. The change also allows passing BigInt values as parameters.

Thanks to Elias Sjögreen who contributed this feature.

SIGINT and SIGBREAK signal listening support on Windows

This release adds the ability to listen for SIGINT (ctrl+c) and SIGBREAK (ctrl+break) on Windows.

Deno.addSignalListener("SIGINT", () => {
  console.log("Received ctrl+c");

Deno.addSignalListener("SIGBREAK", () => {
  console.log("Received ctrl+break");

Read more about OS signals in the manual.

For Deno.kill SIGINT and SIGBREAK support on Windows, please follow issue #14866.

Thanks to Geert-Jan Zwiers and @juzi5201314 for their contributions on this feature.

Support for "deflate-raw" in CompressionStream and DecompressionStream

Deno now suppoorts “The DEFLATE algorithm” RFC1951 via the "deflate-raw" format string as specified in the spec. (Not to be confused with "deflate" which is the “ZLIB Compressed Data Format” RFC1950.)

let input = await"");
const compressed = input.readable.pipeThrough(
  new CompressionStream("deflate-raw"),
let output = await"", { create: true, write: true });

TypeScript 4.7

Deno v1.23 ships with the latest stable version of TypeScript. For more information on new features in TypeScript see TypeScript’s 4.7 blog post

Changes to flags standard module

std/flags started as a fork of minimist and there haven’t been any substantial changes introduced to it until recently.

In this release, there are two breaking changes to the parse function.

collect option

The collect option is a new option to specify that the option argument is collectible. When you specify an argument name in a collect option, then multiple occurrences of the same argument are collected into the array.

import { parse } from "";

console.log(parse(Deno.args, { string: "foo", collect: "foo" }));

This executes as follows:

$ deno run cli.js
{ _: [], foo: [] }
$ deno run cli.js --foo 1
{ _: [], foo: [ "1" ] }
$ deno run cli.js --foo 1 --foo 2
{ _: [], foo: [ "1", "2" ] }
$ deno run cli.js --foo 1 --foo 2 --foo 3
{ _: [], foo: [ "1", "2", "3" ] }

Note that foo in the parsed result always has a string[] type.

Before this change, all arguments were automatically collectible and it had a type of T | T[] where T was typically boolean or string.

We found this behavior not reasonable because in most cases, most arguments are not supposed to be specified multiple times, but because of this behavior, a tool author would always needs to check the type of arguments with Array.isArray().

So this behavior was made optional, and this collecting feature only happens when it’s explicitly specified in the collect option.

To migrate, specify the argument name in the collect option if your CLI tool uses array type arguments.

negatable option

The negatable option is a new option. If you specify the argument name in negatable, then the flags of the form of --no-arg are considered as --arg=false.

Here is an example:

import { parse } from "";
console.log(parse(Deno.args, { boolean: "foo", negatable: "foo" }));

This executes as follows:

$ deno run cli.js
{ _: [], foo: false }
$ deno run cli.js --foo
{ _: [], foo: true }
$ deno run cli.js --no-foo
{ _: [], foo: false }

negatable also can be used with string:

import { parse } from "";
  parse(Deno.args, {
    string: "foo",
    negatable: "foo",
    default: { foo: "bar" },

This executes like:

$ deno run cli.js
{ _: [], foo: "bar" }
$ deno run cli.js --foo baz
{ _: [], foo: "baz" }
$ deno run cli.js --no-foo
{ _: [], foo: false }

foo becomes false even if foo is specified as a string option. This is useful for removing the default value of the option as illustrated in the above example.

This negatable behavior was applied to every argument by default in the previous version, but we found that wasn’t reasonable. So this was changed to be optional.

To migrate, specify the argument name in the negatable option, if your CLI tool depends on this --no-arg type argument.

Thanks to Benjamin Fischer who contributed this feature.

Changes to assertThrows and assertRejects

The signature of assertThrows that accepts an errorCallback as the 2nd argument has been deprecated. Instead assertThrows now returns the thrown error value.

If you use any assertions like the below:

import { assertThrows } from "";

  () => someFunc(),
  (err) => {
    // some assertions about err

Then please update it to the following form:

import { assertThrows } from "";

const err = assertThrows(
  () => someFunc(),

// some assertions about err

The motivation behind this change is that the errorCallback only accepts synchronous assertions on the thrown value. With this change, you can perform whatever assertions on the thrown value, including asynchronous operations.

The same change is also applied to the assertRejects function. The promise it returns now resolves with the error retrieved from the inner promise rejection.

Thanks Mark Ladyshau for contributing this feature.