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 upgrademight 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
- Changes to global variables
windowandprocess - Dependency management
- Permission system changes
- API changes
- CLI changes
- Import assertions are dead, long live import attributes
- Node.js and npm compatibility
- Doc tests with
deno test --doc - TypeScript changes
- Acknowledgements
- What’s next?
🚨️ 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-helpchannel. 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/testing@1.0.2
# 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:preact@10.24.0ℹ️ Using
jsr:ornpm: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:express@5.0.0
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:express@5.0.0
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/testing@1.0.2
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:8These 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:12Deno 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:12Deno.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)
helloThen, 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:4Escape 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:
WebGPUAPIs no longer require--unstable-webgpuflagDeno.dlopen()and other FFI APIs no longer require--unstable-ffiflagDeno.createHttpClient()no longer requires--unstable-httpflag
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:23Deno 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.BufferDeno.close()Deno.copy()Deno.customInspectDeno.fdatasync()andDeno.fdatasyncSync()Deno.File(additionallyDeno.FsFilecan’t be constructed manually anymore)Deno.flock()andDeno.flockSync()Deno.fstat()andDeno.fstatSync()Deno.fsync()andDeno.fsyncSync()Deno.ftruncate()andDeno.ftruncateSync()Deno.funlock()andDeno.funlockSync()Deno.futime()andDeno.futimeSync()Deno.iter()andDeno.iterSync()Deno.metrics()Deno.read()andDeno.readSync()Deno.readAll()andDeno.readAllSync()Deno.resources()Deno.seek()andDeno.seekSync()Deno.shutdown()Deno.write()andDeno.writeSync()Deno.writeAll()andDeno.writeAllSync()
Handling of TLS options was updated, and following options are no longer supported:
Deno.ConnectTlsOptions.certChainDeno.ConnectTlsOptions.certFileDeno.ConnectTlsOptions.privateKeyDeno.ListenTlsOptions.certChainDeno.ListenTlsOptions.certFileDeno.ListenTlsOptions.keyFile
Following interfaces have been removed:
Deno.CloserDeno.ReaderandDeno.ReaderSyncDeno.SeekerandDeno.SeekerSyncDeno.WriterandDeno.WriterSync
Additionally several interfaces related to DNS record handling have been renamed:
Deno.CAARecordtoDeno.CaaRecordDeno.MXRecordtoDeno.MxRecordDeno.NAPTRRecordtoDeno.NaptrRecordDeno.SOARecordtoDeno.SoaRecordDeno.SRVRecordtoDeno.SrvRecord
The “resource IDs” are no longer available in following APIs:
Deno.FsWatcher.prototype.ridDeno.Conn.prototype.ridDeno.TlsConn.prototype.ridDeno.TcpConn.prototype.ridDeno.UnixConn.prototype.ridDeno.FsFile.prototype.ridDeno.Listener.prototype.ridDeno.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()- useDeno.serve()insteadDeno.run()- usenew Deno.Command()insteadDeno.isatty(Deno.stdin.rid)- useDeno.stdin.isTerminal()insteadDeno.isatty(Deno.stdout.rid)- useDeno.stdout.isTerminal()insteadDeno.isatty(Deno.stderr.rid)- useDeno.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 usingvendoroption indeno.jsonfile introduced in Deno v1.37
Several CLI flags are now deprecated:
--lock-write- use--frozeninstead--unstable- use granular--unstable-<feature>flags insteadtest --allow-none- usetest --permit-no-files--jobs- useDENO_JOBSenv var insteadtest --trace-ops- usetest --trace-leaks--ts- use--ext=tsinstead
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.cjsextension. Deno does not look forpackage.jsonfiles andtypeoption 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 installupfront 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.cjsextension. Deno does not look forpackage.jsonfiles andtypeoption 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 supportsrequireing 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_modulesdirectory, 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.tsor 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_modulesdirectory will not work, in addition to any npm dependencies that rely onpostinstallscripts.
The auto mode
deno run --node-modules-dir=auto main.tsor 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_modulesdirectory - mostly projects using bundlers or ones that have npm dependencies withpostinstallscripts.
The manual mode
deno run --node-modules-dir=manual main.tsor 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:
node:cryptoreceived numerous updates:- supports MD4 digests fixing
npm:docusaurus Cipheriv.update(string, undefined)now works correctly- So does
DecipherivwhenautoPaddingoption is disabled - exporting JWK public key is now supported…
- as is importing EC JWK keys…
- and importing JWK octet key pairs…
- and importing RSA JWK keys…
- finally,
npm:web-pushis supported
- supports MD4 digests fixing
async_hooks.AsyncLocalStorageis now more performant and reliablenpm:detect-portandnpm:portfinderare now supportednpm:ssh2gets better support- So does
npm:elastic-apm-node node:wasiis now stubbednode:eventsandnode:utilnow have better coveragenpm:puppeteeris more reliable thanks to addition ofFileHandle#writeFileAPI and better handling of socket upgradesnode:constants,node:fs,node:pathnode:vmandnode:zlibexport missing constantsnode:trace_eventsis now stubbed and doesn’t crashnode:timersnow exportspromisessymbolnpm:qwikandnpm:honoare more reliable thanks to fixes tohttp2modulenpm:playwrightis more stable thanks to support fordetachedoption inchild_processv8module gets better support forSerializerandDeserializerAPIs makingnpm:parcelwork- and many more…
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:
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:
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.

