Skip to main content
Deno 2 is finally here 🎉️
Learn more
Deno 2.0 Release Candidate

Deno 2.0 Release Candidate

UPDATE 2024/10/04: We’ve released several updates to the Deno 2.0 Release Candidate.

We’ve been planning a new major version of Deno for years. Many times, it seemed imminent, but we realized the features we wanted required more work. Now, it’s finally happening. Last month, we released the final 1.x version with 1.46, and today, we’re cutting the release candidate for Deno 2.0, which includes everything we expect in the final release. This is the largest update since 1.0, with major changes like the introduction of Node’s process global. We’ve also made some philosophical shifts, like preferring deno install over the now-deprecated deno cache. Read on for the full list of changes and share your feedback!

To try out the release candidate, run these instructions in your terminal:

deno upgrade
deno upgrade rc

ℹ️ If you are using alternative distribution method like Homebrew, deno upgrade might not be available.

Follow the installation instructions at https://deno.com, then run the aforementioned commands.

What’s new in Deno 2.0 Release Candidate

🚨️ We’re actively seeking feedback 🚨️

This release candidate helps us identify issues prior to the final 2 release. If you encounter any issues or need extra guidance, please create an issue in GitHub or ask in our Discord’s #deno-2-help channel. Our team is actively monitoring both places and will help out as soon as we can.

Changes to global variables

Deno 2 comes with two major changes to global variables — window is gone and Node’s process is now available.


We introduced the window global in Deno v1.0, with the goal being to make Deno as browser-compatible as possible. Unfortunately, the window global variable became a source of problems for users.

Many libraries check if they are executed in the browser by probing for a window global variable instead of checking for the existence of DOM. This led to a class of bugs in libraries that would otherwise work in Deno, due to window being globally available.

Deno started to discourage use of window global in v1.40 suggesting to use globalThis or self instead.

// Deno v1.x
window.addEventListener("load", () => {
  console.log("loaded");
});

// Deno v2.x
globalThis.addEventListener("load", () => {
  console.log("loaded");
});

In contrast, the process global has been widely requested.

While it has been possible to use process by importing it from node:process module for a long time, many popular frameworks rely on its presence in the global scope, often used in configuration files.

Although adding import process from 'node:process'; seems simple, it often causes friction for users of popular frameworks that would otherwise work seamlessly in Deno.

So with addition of process global, you can expect a lot more code written originally for Node.js to work with no changes in Deno. However, we still encourage users to prefer explicit imports. Thus a new no-process-global lint rule was added that will provide hints and quick-fixes in your editor to use an import statement instead.

Dependency management

Deno 2 comes with several new features that improve dependency management.

The deno add subcommand now handles specifiers with a subpath:

# Before in Deno v1.46
deno add jsr:@std/testing/snapshot
error: Failed to parse package required: @std/testing/snapshot

Caused by:
    0: Invalid package requirement '@std/testing/snapshot'. Invalid version requirement. Invalid specifier version requirement. Unexpected character '/'
    ...

# Deno v2.0
deno add jsr:@std/testing/snapshot
Add jsr:@std/[email protected]

# Deno v1.46
deno add npm:preact/hooks
error: Failed to parse package required: npm:preact/hooks

Caused by:
    0: Invalid package requirement 'preact/hooks'. Packages in the format <scope>/<name> must start with an '@' symbol.
    1: Packages in the format <scope>/<name> must start with an '@' symbol.

# Deno v2.0
deno add npm:preact/hooks
Add npm:[email protected]

ℹ️ Using jsr: or npm: prefixes is now required when adding dependencies to avoid potential ambiguity between packages with same names in both registries.

If you omit the prefix Deno will print a suggestion with correct invocation, checking which registries contain the package.

Additionally, if your project contains package.json file, Deno will prefer adding npm: dependencies to package.json, rather than deno.json.

cat package.json
{
  "dependencies": {}
}

deno add npm:express
Add npm:[email protected]

cat package.json
{
  "dependencies": { "express": "^5.0.0" }
}

You can also add “dev dependencies” to package.json using the --dev flag:

deno add --dev npm:express
Add npm:[email protected]

cat package.json
{
  "devDependencies": { "express": "^5.0.0" }
}

deno install now supports the --entrypoint flag, which allows you to install all dependencies from a given module(s):

// main.ts
import snapshot from "jsr:@std/testing/snapshot";
import express from "npm:express";
deno install --entrypoint main.ts
Download ...

A new deno remove subcommand has been added to quickly remove some of the dependencies:

deno add jsr:@std/testing
Added jsr:@std/[email protected]

cat deno.json
{
  "imports": { "@std/testing": "jsr:@std/testing@^1.0.2" }
}

deno remove @std/testing
Removed @std/testing

cat deno.json
{}

You can also use deno remove to handle dependencies listed in package.json.


Deno 2 ships with a new, more concise lockfile format (v4), that will minimize diffs when updating dependencies and ensure reproducible builds.

cat deno.lock
{
  "version": "4",
  "specifiers": {
    "jsr:@std/assert@^1.0.4": "1.0.5",
    "jsr:@std/data-structures@^1.0.2": "1.0.4",
    "jsr:@std/fs@^1.0.3": "1.0.3",
    "jsr:@std/internal@^1.0.3": "1.0.3",
    "jsr:@std/path@^1.0.4": "1.0.6",
    "jsr:@std/testing@*": "1.0.2",
    "jsr:@std/testing@^1.0.2": "1.0.2"
  },
  // ...
}

Deno will automatically migrate you to the new lockfile format.


Finally, Deno has improved its error messaging, providing helpful hints for common issues like incorrectly formatted relative import paths, or missing dependencies when using “bare specifiers”:

// main.ts
import "@std/dotenv/load";
deno run main.ts
error: Relative import path "@std/dotenv/load" not prefixed with / or ./ or ../
  hint: Try running `deno add jsr:@std/dotenv/load`
    at file:///main.ts:1:8

These updates collectively streamline the process of managing dependencies in Deno projects, making it more intuitive and aligned with modern development workflows.

Permission system changes

New Deno.errors.NotCapable error

Deno’s permission system is one of its most loved features. When a program tries to access an API that was not allowed using --allow-* flags an error is raised. In Deno v1.x, this was Deno.errors.PermissionDenied.

There was one problem with that though. All operating systems raise this error too, for example when your user does not have access to an admin-only file, or when you are trying to listen on a privledged port. Because of this, users were often confused to see that error raised despite running with --allow-all flag.

In Deno v2.0, a lack of Deno permissions now raises the Deno.errors.NotCapable error instead to make it easier to discriminate between OS-level errors and Deno errors.

await Deno.readTextFile("./README.md");

Deno v1.46

$ deno run main.ts
error: Uncaught (in promise) PermissionDenied: Requires read access to "./README.md", run again with the --allow-read flag
await Deno.readTextFile("./README.md")
           ^
    at Object.readTextFile (ext:deno_fs/30_fs.js:878:24)
    at file:///main.ts:1:12

Deno v2.0

$ deno run main.ts
error: Uncaught (in promise) NotCapable: Requires read access to "./README.md", run again with the --allow-read flag
await Deno.readTextFile("./README.md")
           ^
    at Object.readTextFile (ext:deno_fs/30_fs.js:777:24)
    at file:///main.ts:1:12

Deno.mainModule doesn’t require --allow-read permission

Permissions check for Deno.mainModule API, which gives you a full path of the main module, has been relaxed and no longer requires full --allow-read permission. This also applies to process.argv API.

This requirement is deprecated, but the path to the main module can be obtained by creating an Error instance and inspecting its stack trace.

--allow-hrtime flag is gone

Deno v1.x had an --allow-hrtime flag that affected APIs like performance.now() providing high resolution timing. In Deno 2, this flag is gone and these APIs always provide high resolution timing.

This flag was deprecated as high-resolution timing can be achieved using standard JavaScript APIs like Worker and SharedArrayBuffer.

--allow-run flag changes

There are a few big changes for --allow-run flag to guide you towards safer execution of subprocesses.

First, a warning will appear if you use the --allow-run flag without specifying an allow list of binary names or paths to binaries:

new Deno.Command("echo", { args: ["hello"] }).spawn();
$ deno run --allow-run main.ts
Warning --allow-run without an allow list is susceptible to exploits. Prefer specifying an allow list (https://docs.deno.com/runtime/fundamentals/security/#running-subprocesses)
hello

Then, anytime a subprocess is spawned with LD_* or DYLD_* environmental variables, a full --allow-run permission will be required. Note that these environmental variables should rarely be relied on. If you really need to use them, you should consider further sandboxing Deno by running additional layers of protection (e.g. a Docker container).

new Deno.Command("echo", {
  env: {
    "LD_PRELOAD": "./libpreload.so",
  },
}).spawn();
$ deno run --allow-run=echo main.ts
error: Uncaught (in promise) NotCapable: Requires --allow-all permissions to spawn subprocess with LD_PRELOAD environment variable.
}).spawn();
   ^
    at spawnChildInner (ext:runtime/40_process.js:182:17)
    at spawnChild (ext:runtime/40_process.js:205:10)
    at Command.spawn (ext:runtime/40_process.js:479:12)
    at file:///main.ts:5:4

Escape commas in file names

It is now possible to grant permissions for reading and writing files that contain commas in the file name.

In Deno v1.x, a comma was used to delimit between multiple file names:

deno run --allow-read=file1.txt,file2.txt
# grants permission to read `file1.txt` and `file2.txt`

making it impossible to grant permission to a file that contains a comma in its name.

In Deno 2 this can be done by escaping a comma with another comma:

deno run --allow-read=file,,.txt,file2.txt
# grants permission to read `file,.txt` and `file2.txt`

API changes

Stable APIs

Several APIs have been stablized in this Deno 2:

  • WebGPU APIs no longer require --unstable-webgpu flag
  • Deno.dlopen() and other FFI APIs no longer require --unstable-ffi flag
  • Deno.createHttpClient() no longer requires --unstable-http flag

The error messages have been improved as well, providing useful hints when you try to use an unstable API without a corresponding flag:

const db = await Deno.openKv();

Deno v1.46

$ deno run db.ts
error: Uncaught (in promise) TypeError: Deno.openKv is not a function
const db = await Deno.openKv();
                      ^
    at file:///db.ts:1:23

Deno v2.0

error: Uncaught (in promise) TypeError: Deno.openKv is not a function
const db = await Deno.openKv();
                      ^
    at file:///db.ts:1:23

    info: Deno.openKv() is an unstable API.
    hint: Run again with `--unstable-kv` flag to enable this API.

Breaking changes to Deno APIs

ℹ️ For more information how to migrate away from the deprecated APIs visit the Deno 1 to 2 Migration Guide

Following APIs have been removed:

  • Deno.Buffer
  • Deno.close()
  • Deno.copy()
  • Deno.customInspect
  • Deno.fdatasync() and Deno.fdatasyncSync()
  • Deno.File (additionally Deno.FsFile can’t be constructed manually anymore)
  • Deno.flock() and Deno.flockSync()
  • Deno.fstat() and Deno.fstatSync()
  • Deno.fsync() and Deno.fsyncSync()
  • Deno.ftruncate() and Deno.ftruncateSync()
  • Deno.funlock() and Deno.funlockSync()
  • Deno.futime() and Deno.futimeSync()
  • Deno.iter() and Deno.iterSync()
  • Deno.metrics()
  • Deno.read() and Deno.readSync()
  • Deno.readAll() and Deno.readAllSync()
  • Deno.resources()
  • Deno.seek() and Deno.seekSync()
  • Deno.shutdown()
  • Deno.write() and Deno.writeSync()
  • Deno.writeAll() and Deno.writeAllSync()

Handling of TLS options was updated, and following options are no longer supported:

  • Deno.ConnectTlsOptions.certChain
  • Deno.ConnectTlsOptions.certFile
  • Deno.ConnectTlsOptions.privateKey
  • Deno.ListenTlsOptions.certChain
  • Deno.ListenTlsOptions.certFile
  • Deno.ListenTlsOptions.keyFile

Following interfaces have been removed:

  • Deno.Closer
  • Deno.Reader and Deno.ReaderSync
  • Deno.Seeker and Deno.SeekerSync
  • Deno.Writer and Deno.WriterSync

Additionally several interfaces related to DNS record handling have been renamed:

  • Deno.CAARecord to Deno.CaaRecord
  • Deno.MXRecord to Deno.MxRecord
  • Deno.NAPTRRecord to Deno.NaptrRecord
  • Deno.SOARecord to Deno.SoaRecord
  • Deno.SRVRecord to Deno.SrvRecord

The “resource IDs” are no longer available in following APIs:

  • Deno.FsWatcher.prototype.rid
  • Deno.Conn.prototype.rid
  • Deno.TlsConn.prototype.rid
  • Deno.TcpConn.prototype.rid
  • Deno.UnixConn.prototype.rid
  • Deno.FsFile.prototype.rid
  • Deno.Listener.prototype.rid
  • Deno.TlsListener.prototype.rid

Finally, a few APIs have been “soft-deprecated”. These APIs will keep working, but will no longer receive updates or bug fixes. It is highly recommended and encouraged to migrate to stable counterparts:

  • Deno.serveHttp() - use Deno.serve() instead
  • Deno.run() - use new Deno.Command() instead
  • Deno.isatty(Deno.stdin.rid) - use Deno.stdin.isTerminal() instead
  • Deno.isatty(Deno.stdout.rid) - use Deno.stdout.isTerminal() instead
  • Deno.isatty(Deno.stderr.rid) - use Deno.stderr.isTerminal() instead

Command Line Interface changes

Deno 2 removes support for two subcommands:

  • deno bundle - this subcommand was deprecated in Deno v1.31 due to mismatch in expectations of users and the actual functionality provided by the built-in bundler.

    Many Deno users expected a general-purpose, highly customizable bundler. However Deno’s built-in bundler was meant as a simple tool to concatenate multiple files into a single-file for easier distribution; there were not settings to customize any behavior either.

    We plan to implement a new built-in bundler, so look out for updates in future releases.

  • deno vendor - was deprecated in Deno v1.45 and superseded by a much easier solution of using vendor option in deno.json file introduced in Deno v1.37

Several CLI flags are now deprecated:

  • --lock-write - use --frozen instead
  • --unstable - use granular --unstable-<feature> flags instead
  • test --allow-none - use test --permit-no-files
  • --jobs - use DENO_JOBS env var instead
  • test --trace-ops - use test --trace-leaks
  • --ts - use --ext=ts instead

Additionally, you can enable debug logging by using DENO_LOG environmental variable, instead of RUST_LOG.

Lastly, the files options in the config file are now deprecated.

In Deno v1.x this syntax was supported:

{
  "test": {
    "files": {
      "include": ["**/*.ts"],
      "exclude": ["ignore.ts"]
    }
  }
}

In Deno 2, the files configuration has been simplified, being flattened into the parent configuration:

{
  "test": {
    "include": ["**/*.ts"],
    "exclude": ["ignore.ts"]
  }
}

Import assertions are dead, long live import attributes

Import Assertions support was deprecated in Deno v1.46 and is no longer available in Deno 2.

The reason being, the proposal has undergone major changes, including updating the keyword from assert to with, and being renamed to Import Attributes, additionally some browsers (eg. Chrome) had already unshipped support for Import Assertions too.

Here’s what the update looks like:

- import data from "./data.json" assert { type: "json" };
+ import data from "./data.json" with { type: "json" };

Node.js and npm compatibility

Improved CommonJS support

Since its 1.0 release, Deno has prioritized ES modules as the primary module system, offering only limited support for CommonJS through manual creation of require:

import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
require("...");

While we firmly believe that ES modules are the future of JavaScript, the landscape in 2024 still includes numerous libraries and projects relying on CommonJS.

Despite Deno’s robust handling of CJS libraries over the past year, users occasionally faced challenges when integrating CommonJS, particularly within their own codebases.

In Deno 2, several improvements were made to help working with CommonJS modules and make transition to ES modules easier:

  • deno run index.cjs - Deno can now execute CommonJS files, provided that they use .cjs extension. Deno does not look for package.json files and type option to determine if the file is CommonJS or ESM.

    When using this option, Deno will also not automatically install dependencies, so you will need to manually run deno install upfront to ensure all required dependencies are available.

// index.cjs
const express = require("express");

NOTE: Deno permissions system is still in effect, so require() calls will prompt for --allow-read permissions.

deno run index.cjs
┏ ⚠️  Deno requests read access to "/dev/example".
┠─ Learn more at: https://docs.deno.com/go/--allow-read
┠─ Run again with --allow-read to bypass this prompt.
┗ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions) >
  • import cjs from "./index.cjs" - Deno can now import CommonJS files, provided that they use .cjs extension. Deno does not look for package.json files and type option to determine if the file is CommonJS or ESM.
// greet.cjs
module.exports = {
  hello: "world",
};
import greet from "./greet.cjs";
console.log(greet);
deno run main.js
{
  "hello": "world"
}
  • require(ESM) - Deno now supports requireing ES modules. This is only possible if the modules required do not use Top-Level Await. This change should improve interoperability when using mixed dependencies that rely on CommonJS and ESM.
// greet.js
export function greet(name) {
  return `Hello ${name}`;
}
// esm.js
import { greet } from "./foo.js";

export { greet };
// main.cjs
const esm = require("./esm");
console.log(esm);
console.log(esm.greet("Deno"));
deno run --allow-read main.cjs
[Module: null prototype] { greet: [Function: greet] }
Hello Deno
  • Better suggestions for errors when dealing with CommonJS modules, that guide you towards a working program:
// main.js
module.exports = {
  foo: "foo",
};
deno run main.js
error: Uncaught (in promise) ReferenceError: module is not defined
module.exports = {
^
    at file:///Users/ib/dev/deno/main.js:1:1

    info: Deno does not support CommonJS modules without `.cjs` extension.
    hint: Rewrite this module to ESM or change the file extension to `.cjs`.

Bring your own node_modules is the default

The “bring your own node modules” functionality was introduced in Deno v1.38.

ℹ️ While we still strongly advocate to not rely on local node_modules directory, a lot of existing projects and frameworks operate on the assumption that this directory will be present.

But sometimes you still want to have a local node_modules directory even if you don’t have a package.json - eg. when using frameworks like Next.js, Remix or Svelte; or when depending on npm packages that use Node-API like duckdb, sqlite3, esbuild and others. To improve compatibility, if a project contains package.json file, Deno will expect that node_modules directory will be set up manually.


In previous Deno release you could use --node-modules-dir flag or nodeModulesDir option in the config file to tell Deno if the node_modules directory should be created or not.

Deno 2 changes this configuration option - it’s no longer a boolean, but rather an enum with three options:

The default mode

deno run main.ts

# or

deno run --node-modules-dir=none main.ts

or with a configuration file

{
  "nodeModulesDir": "none"
}

This is the default mode for Deno-first projects - ie. projects that do not have package.json file. It automatically installs dependencies into the global cache and doesn’t create a local node_modules directory.

ℹ️ Recommended for new projects. Note that frameworks that expect a node_modules directory will not work, in addition to any npm dependencies that rely on postinstall scripts.

The auto mode

deno run --node-modules-dir=auto main.ts

or with a configuration file

{
  "nodeModulesDir": "auto"
}

The auto mode automatically install dependencies into the global cache and creates a local node_modules directory in the project root.

ℹ️ Recommended for projects that have npm dependencies that rely on node_modules directory - mostly projects using bundlers or ones that have npm dependencies with postinstall scripts.

The manual mode

deno run --node-modules-dir=manual main.ts

or with a configuration file

{
  "nodeModulesDir": "manual"
}

The manual mode is the default mode for projects using package.json. This mode is the workflow that Node.js uses and requires an explicit installation step using deno install/npm install/pnpm install or any other package manager.

ℹ️ Recommended for projects using frameworks like Next.js, Remix, Svelte, Qwik, etc; or tools like Vite, Parcel, Rollup.


It is recommended to use the default none mode, and fallback to auto or manual mode if you get errors complaining about missing packages inside node_modules directory.

Node.js APIs compatibility

Once again, a great progress was made in terms of supported built-in Node.js APIs, resulting in even more popular packages being supported:

Doc tests with deno test --doc

JSDoc is a format for writing inline documentation for JavaScript and TypeScript. It’s how deno doc and JSR automatically generate documentation for its modules.

JSDoc allows us to write code examples directly in comments:

/**
 * ```ts
 * import { assertEquals } from "jsr:@std/assert/equals";
 *
 * assertEquals(add(1, 2), 3);
 * ```
 */
export function add(a: number, b: number) {
  return a + b;
}

There’s one problem though… How do you ensure that these examples stay up to date and don’t code-rot?

Deno v1.10 added feature to type-check the examples, which helped the situation, but did not solve it completely. The only way to ensure the examples are up to date is to actually execute them.

In Deno 2, deno test --doc not only type-checks the examples in JSDoc, but also executes them:

Demo of testing JSDoc examples in Deno 2

To test your JSDoc examples, simply run deno test --doc and Deno will automatically discover and run relevant codeblocks.

ℹ️ If you want to only type-check your examples, you can still do so by running deno check --doc.

But that’s not all! In addition to testing JSDoc example, you can also execute codeblocks in Markdown files:

Demo of testing Markdown codeblocks in Deno 2

This is the first iteration of this feature and we’d appreciate any feedback.

TypeScript changes

Deno v2.0 ships with TypeScript 5.6, you can read about it more in Announcing TypeScript 5.6 blog post.

To help catch common pitfalls, these TypeScript settings are now enabled by default:

Additionally, Deno 2 ships with built-in support for @types/node at version 22 to make it easier to type-check code relying on Node.js APIs.

Finally, to ensure your compilerOptions settings are up-to-date, we’ve made it an allow list, so Deno will complain and flag it if you use an unsupported option.

Acknowledgments

We couldn’t build Deno without the help of our community! Whether by answering questions in our community Discord server or reporting bugs, we are incredibly grateful for your support. In particular, we’d like to thank the following people for their contributions to the Deno 2 release candidate: Andreas Deininger, Armaan Salam, Bedis Nbiba, Birk Skyum, Bob Callaway, Caleb Cox, Caleb Lloyd, Coty, Hajime-san, HasanAlrimawi, Ian Bull, Ivancing, Jake Abed, Kamil Ogórek, Kenta Moriuchi, Kyle Kelley, Mohammad Sulaiman, MrEconomical, MujahedSafaa, Pig Fang, Rano | Ranadeep, Roy Ivy III, Sean McArthur, Sʜɪᴍᴜʀᴀ Yū, Victor Turansky, Yazan AbdAl-Rahman, Zebreus, chirsz, cions, i-api, melbourne2991, seb, vwh, and Łukasz Czerniawski.

Would you like to join the ranks of Deno contributors? Check out our contribution docs here, and we’ll see you on the list next time.

Thank you for catching up with our Deno 2 release candidate, and we hope you love building with Deno!

What’s next?

This release candidate is working towards our 2.0 release and it should be expected that there will be bugs and issues. If you encounter any, please let us know in GitHub issues (with the 2.0 tag) or on our Discord’s dedicated #deno-2-help channel. We’re actively monitoring both areas to ensure that all major bugs are fixed prior to our 2.0 release.