This guide is the operator-and-developer reference for the Deno runtime: how it is built, how the source is organized, how to deploy and operate it, what its security model guarantees, and how to extend or contribute to it. Every fact is taken from the source tree at commit 7529c84 (Deno CLI version 2.7.12).
1. Executive Summary
Deno is a JavaScript / TypeScript / WebAssembly runtime distributed as a single self-contained binary. The codebase is a 70+ crate Rust workspace combining V8 (the JavaScript engine), Tokio (the async runtime), and a substantial set of in-house Rust crates that implement Web APIs, Node compatibility, the package management system, the language tooling, and the editor language server. The whole binary statically links V8.
Top-level facts:
| Item | Value |
|---|---|
| License | MIT |
| Primary languages | Rust, TypeScript, JavaScript |
| Build system | Cargo workspace (resolver = "2") |
| Edition | Rust 2024 |
| JavaScript engine | V8 (v8 crate, currently v147.x) |
| Async runtime | Tokio |
| TypeScript transpiler | deno_ast (SWC-based) + deno_typescript_go_client (TypeScript-Go for type-checking) |
| Distribution | Single-binary releases for 5 target triples; install scripts; brew/winget/scoop/chocolatey |
| Workspace member count | ~70 (cli, runtime, ext/, libs/, tests/*) |
| CLI subcommand count | 38 (see Subcommand Architecture below) |
| Permission categories | 9 (read, write, net, env, sys, run, ffi, hrtime, import + all/scripts) |
| Documented env vars | 26 (see Environment Variables in User Guide) |
Web platform extensions (ext/) |
32 crates |
Shared library crates (libs/) |
24 crates |
The Deno CLI binary (cli/main.rs) is just deno::main() — a thin wrapper that calls into the deno library crate (cli/lib.rs). Everything else is layered crates.
Note
This guide focuses on the project as it is checked in. Deno Deploy (the hosted runtime) is a separate product and is treated here only as a destination for
deno deploy.
2. Architecture
2.1 Layered Architecture
+----------------------------------------------------------------------+
| cli/ (binary) |
| CLI parsing (clap), tools/* (run, fmt, lint, test, compile, ...), |
| factory wiring, LSP, type-checker driver |
+----------------------------------------------------------------------+
| runtime/ (deno_runtime crate) |
| MainWorker, WebWorker, Deno.* namespace, permissions enforcement, |
| bootstrap JS, integrates ext/* into a JsRuntime |
+----------------------------------------------------------------------+
| ext/ (32 crates: web/fetch/net/http/fs/crypto/node/webgpu/...) |
| Each crate contributes ops + JS/TS bindings for one Web API or |
| Node compatibility surface; loaded as deno_core::Extension |
+----------------------------------------------------------------------+
| libs/core/ (deno_core crate) |
| JsRuntime, ExtensionBuilder, op2 macro, ModuleLoader, snapshotting, |
| the ops bridge between JS and Rust |
+----------------------------------------------------------------------+
| v8 crate |
| Rust bindings to the V8 JavaScript engine (statically linked) |
+----------------------------------------------------------------------+
Below the engine, Tokio drives all async I/O. Above the CLI, no other layers exist — the binary IS the user interface.
2.2 Component Inventory
cli/ — the user-facing binary
| Module | Purpose |
|---|---|
main.rs |
Entry point. Calls deno::main(). |
lib.rs |
Top-level orchestration: parse flags, build CliFactory, dispatch to subcommand tools. |
args/flags.rs |
Clap definitions for every subcommand and flag (~15 KLOC). The single source of truth for the CLI surface. |
args/mod.rs |
CliOptions, CliLockfile, env-var resolution, config file discovery. |
factory.rs |
CliFactory — lazy DI container that builds the resolver, module loader, npm installer, type checker, etc. on demand. |
tools/ |
One module per subcommand: run.rs, serve.rs, task.rs, repl.rs, compile/, bundle/, bench.rs, lint/, init.rs, installer.rs, jupyter.rs, pm.rs (add/remove/install/outdated), publish/. |
lsp/ |
The deno lsp language server (Tower-LSP). |
tsc/ |
TypeScript compiler integration (TS-Go IPC + the legacy deno_tsc wrapper). |
module_loader.rs |
Implements deno_core::ModuleLoader for the CLI: handles JSR / npm / HTTPS / file URLs. |
npm.rs, node.rs, jsr.rs |
Per-scheme resolvers built atop the libs/resolver crate. |
cache/ |
Disk caches (deps, type-check, emit, npm, codecov). |
worker.rs |
Constructs the LibMainWorkerFactory from deno_lib. |
standalone/ |
Loader for deno compile binaries (reads the eszip appended to the executable). |
rt/ |
denort runtime variant — minimal CLI used inside compiled binaries. |
runtime/ — the embeddable runtime (deno_runtime crate)
| Item | Purpose |
|---|---|
worker.rs |
MainWorker — the foreground JS worker. Wraps JsRuntime, sets up the Deno namespace, runs bootstrap JS. |
web_worker.rs |
WebWorker — implements the standard Worker Web API in a dedicated OS thread. |
worker_bootstrap.rs |
The Rust side of the bootstrap (passes runtime metadata to JS). |
js/ |
Bootstrap JavaScript files (99_main.js, etc.) executed before user code to construct the Deno namespace. |
permissions.rs, permissions/ |
deno_permissions integration: parsing flags, enforcing checks for ops. |
ops/ |
Ops registered directly by the runtime (FS metadata, OS info hooks). |
cpu_profiler/ |
V8 CPU profiler integration. |
subprocess_windows/ |
Windows-specific subprocess infrastructure (CreateProcessW wrapper). |
transpile.rs |
Optional in-runtime TS→JS transpilation (HMR, REPL). |
snapshot.rs |
Build-time and load-time V8 snapshot machinery. |
The runtime/README.md notes that this crate is deliberately a slimmed-down version of the CLI: it omits TypeScript type-checking, the doc/lint/fmt tools, and CLI argument parsing — it provides only "JavaScript execution with Deno's operating system bindings (ops)." Third parties embed this crate to get a Deno-compatible MainWorker without dragging in the whole cli/.
ext/ — Web platform and Node compatibility extensions (32 crates)
Each ext/<name> crate contributes ops + JS bindings for one Web API surface (or Node-compatibility surface). They are loaded as deno_core::Extension instances when the JsRuntime is built.
| Crate | Surface |
|---|---|
ext/web |
DOM-style globals: URL, URLSearchParams, TextEncoder, TextDecoder, Blob, File, FileReader, EventTarget, Event, MessageEvent, AbortController, AbortSignal, structured-clone, performance timeline, queueMicrotask. |
ext/console |
The console global. |
ext/url |
URL/URLSearchParams (separate so ext/web can compose). |
ext/webidl |
IDL conversion helpers used by other ext crates. |
ext/fetch |
fetch, Request, Response, Headers, Body. Backed by reqwest/hyper. |
ext/net |
Deno.connect, Deno.listen, TCP/UDP/Unix sockets. |
ext/tls |
TLS configuration shared between fetch and net. |
ext/http |
The Hyper-based HTTP server backing Deno.serve. |
ext/websocket |
WebSocket client and server. |
ext/webstorage |
localStorage / sessionStorage (SQLite-backed). |
ext/broadcast_channel |
BroadcastChannel Web API. |
ext/crypto |
Web Crypto API (crypto.subtle). |
ext/fs |
Filesystem ops (Deno.readFile, Deno.writeFile, etc.). |
ext/io |
Stream IO primitives. |
ext/cache |
Web Cache API. |
ext/cron |
Deno.cron (unstable). |
ext/kv |
Deno.openKv (unstable, SQLite-backed locally). |
ext/os |
OS info / Deno.osRelease, Deno.hostname, etc. |
ext/process |
Deno.Command, Deno.spawn. |
ext/signals |
Signal handling (Deno.addSignalListener). |
ext/ffi |
Deno.dlopen (foreign-function interface). |
ext/napi, ext/napi/sym |
N-API for native Node addons. |
ext/node |
Node compatibility — node:fs, node:http, node:buffer, etc. polyfills + ops. The largest single ext crate. |
ext/node_crypto |
Node crypto module (separated for size). |
ext/node_sqlite |
Node sqlite built-in. |
ext/image |
Image decode (powers createImageBitmap). |
ext/webgpu |
WebGPU. |
ext/telemetry |
OpenTelemetry tracing/metrics for the runtime. |
ext/bundle |
Bundling APIs (powers deno bundle). |
ext/rt_helper |
Internal helpers for denort (compiled-binary runtime). |
The naming convention is consistent: ext/<name>/lib.rs defines an Extension via deno_core::Extension::builder(); ext/<name>/<name>.js (or .ts) is the JS portion; ops are written in Rust with the #[op2] macro from libs/ops.
libs/ — shared library crates (24 crates)
These are the crates that the CLI and the runtime depend on. Several are also published independently to crates.io (deno_core, deno_ast, deno_lockfile, etc.).
| Crate | Purpose |
|---|---|
libs/core |
deno_core — the JsRuntime, op infrastructure, snapshotting, the ModuleLoader trait. The foundation of everything. |
libs/core_testing, libs/core/examples, libs/core/io, libs/core/arena, libs/core/runtime, libs/core/modules |
deno_core sub-modules and helpers. |
libs/ops, libs/ops/op2, libs/ops/conversion, libs/ops/webidl, libs/ops/compile_test_runner |
The op2! proc-macro and op-definition machinery used by every ext/ crate. |
libs/serde_v8, libs/serde_v8/magic, libs/serde_v8/utilities, libs/serde_v8/examples |
Zero-copy serialization between V8 values and Rust types. |
libs/resolver, libs/resolver/loader, libs/resolver/cache, libs/resolver/cjs, libs/resolver/npm |
Module resolution: deno.json discovery, import map application, JSR / npm / file resolution. |
libs/config, libs/config/deno_json, libs/config/workspace, libs/config/glob |
Parses and validates deno.json[c], manages workspaces, resolves include/exclude globs. |
libs/lockfile |
deno.lock parsing, integrity checking, and rewriting. |
libs/cache_dir, libs/cache_dir/global, libs/cache_dir/file_fetcher |
The $DENO_DIR cache implementation (deps, npm, type-check, emit). |
libs/npm, libs/npm/resolution, libs/npm/examples |
npm package resolution and metadata. |
libs/npm_cache |
Disk caching of npm packages. |
libs/npm_installer |
Installs packages into node_modules/ (when --node-modules-dir=auto). |
libs/npmrc |
.npmrc parser for registry/auth config. |
libs/node_resolver |
Implements Node's resolution algorithm (extensionless imports, package.json exports/imports). |
libs/node_shim |
Provides Node-compatible globals where necessary. |
libs/package_json |
package.json parsing. |
libs/dotenv |
.env file loader. |
libs/dcore, libs/dcore/src |
Lower-level deno helpers shared between cli and runtime. |
libs/eszip, libs/eszip/examples |
The eszip format used by deno compile and the bundler. |
libs/inspector_server |
Standalone V8 inspector server. |
libs/typescript_go_client, libs/typescript_go_client/src |
Rust client that talks to TypeScript-Go (the Go-based TS type checker bundled with the CLI). |
libs/crypto |
Crypto helpers shared by ext/crypto and ext/node_crypto. |
libs/maybe_sync |
Cross-thread synchronization helpers. |
libs/napi_sys, libs/napi_sys/src |
Low-level N-API definitions. |
tests/ — integration test infrastructure
| Folder | Purpose |
|---|---|
tests/integration |
The Rust-side integration test runner used by cargo test. |
tests/specs |
The spec-based "files in directory = one test" suite (~5000+ specs). Each spec has a __test__.jsonc describing args, env, expected stdout/stderr/exit. |
tests/unit, tests/unit_node |
JS/TS unit tests run inside Deno itself. |
tests/wpt |
Web Platform Tests subset. |
tests/napi |
N-API addon test programs. |
tests/ffi, tests/sqlite_extension |
FFI and sqlite-extension test addons. |
tests/bench |
Microbenchmarks. |
tests/registry |
Local mock JSR/npm registry used by tests. |
tests/util/server, tests/util/lib |
Test HTTP servers and helpers. |
tests/node_compat |
Subset of upstream Node tests run for compatibility coverage. |
2.3 Request Lifecycle: deno run main.ts
$ deno run --allow-net main.ts
|
v
cli/main.rs::main() --> deno::main() (cli/lib.rs)
|
v
flags_from_vec_with_initial_cwd(args) parses clap, returns Flags struct
|
v
CliFactory::from_flags(flags) lazy DI container
|
v
factory.create_main_worker_factory() builds LibMainWorkerFactory
| - Resolves deno.json (if present)
| - Builds module loader (handles file://, https://, jsr:, npm:, node:)
| - Builds permission set from flags + deno.json
| - Builds NPM installer if needed
|
v
worker.create_main_worker(...) MainWorker (deno_runtime)
| - Constructs JsRuntime (deno_core)
| - Loads all ext/* extensions (web, fetch, net, http, fs, crypto, node, ...)
| - Snapshot is loaded if available, otherwise extensions are bootstrapped fresh
| - Runs runtime/js/99_main.js to set up the Deno namespace
| - Applies bootstrap options (args, location, runtime version)
|
v
main_worker.execute_main_module(specifier)
| - Module loader fetches main.ts (from disk)
| - If TS/TSX, deno_ast transpiles to JS (cached in $DENO_DIR/gen)
| - JsRuntime instantiates and evaluates the module graph
| - Top-level await is awaited
|
v
main_worker.run_event_loop(false) drives Tokio + V8 microtasks until idle
|
v
Process exits with main_worker.exit_code()
The same orchestration drives every executed-script subcommand (run, serve, test, bench, eval, repl, task). Each builds slightly different WorkerOptions and may register additional setup (test runner installs Deno.test, REPL installs the REPL state, etc.).
2.4 Subcommand Architecture
Every subcommand has:
- A clap
Command::new(...)builder incli/args/flags.rs(lines 2219+, onefn <cmd>_subcommand()per subcommand). - A
<cmd>_parse(...)function (also inflags.rs) that consumes the parsedArgMatchesand writes intoFlags. - A handler in
cli/tools/<cmd>.rs(orcli/tools/<cmd>/mod.rsfor multi-file subcommands).
The 38 subcommands defined in the root command (flags.rs:2140-2179):
| Group | Subcommands |
|---|---|
| Execution | run, serve, task, repl, eval |
| Dependency mgmt | add, install, uninstall, outdated, approve-scripts, remove, update |
| Tooling | bench, bundle, cache, check, clean, compile, coverage, doc, fmt, info, init, create, jupyter, lint, test, publish, vendor |
| Cloud / sandbox | deploy, sandbox |
| Lifecycle | upgrade, lsp, types, completions |
| Aliases / hidden | x (alias for npm:), json_reference (hidden, used for docs site generation) |
Aliases: When the CLI is invoked as dx, denox, or dnx, flags_from_vec_with_initial_cwd rewrites the args to insert x as the first argument (flags.rs:1697-1711). This makes dx eslint . equivalent to deno x eslint . (which itself is deno run npm:eslint .).
2.5 The op2 Bridge: How JS Calls Rust
Every Deno API that does I/O goes through an op — an asynchronous or synchronous function defined in Rust and exposed to JS through V8 fast-call bindings.
// ext/fs/lib.rs (illustrative)
#[op2(async)]
async fn op_read_file_async(
#[state] state: &PermissionsContainer,
#[string] path: String,
) -> Result<Vec<u8>, AnyError> {
state.check_read(Path::new(&path), "Deno.readFile()")?;
tokio::fs::read(path).await.map_err(Into::into)
}
The #[op2] macro generates V8 fast-call entry points and serde_v8 bindings. The op is registered with an Extension:
deno_core::extension!(
deno_fs,
deps = [deno_web, deno_io],
ops = [op_read_file_async, op_write_file_async, ...],
esm_entry_point = "ext:deno_fs/30_fs.js",
esm = ["30_fs.js"],
options = { fs: Arc<dyn FileSystem> },
);
When the CLI builds a JsRuntime, it passes a list of these extensions. At snapshot time, the JS portions are evaluated and snapshotted. At runtime, only the Rust ops + any non-snapshotted extensions remain to load.
2.6 Snapshotting
Building the binary involves a build-time JavaScript snapshot: all bootstrap JS for deno_core + every ext/* is parsed and serialized into a binary blob that ships inside the executable. At startup, V8 deserializes the snapshot, skipping the cost of parsing and evaluating ~2 MB of JS. The cli Cargo.toml enables include_js_files_for_snapshotting only on its build-dependencies, so the snapshot is generated by build.rs and the embedded blob is the only JS that ships.
Disable with the hmr Cargo feature for development: this drops the snapshot and reloads JS from disk, which is slower but allows live editing.
3. System Requirements
3.1 Runtime requirements (running deno)
| Resource | Minimum | Notes |
|---|---|---|
| OS | Linux 4.18+, macOS 11+, Windows 10+ | x86_64 and ARM64 supported on Linux/macOS; x86_64 only on Windows |
| RAM | 64 MB free | V8 baseline; real apps need more |
| Disk | ~150 MB for binary + cache headroom in $DENO_DIR |
deno compile outputs are 80–100 MB each |
| libc (Linux) | glibc 2.28+ | Pre-built binaries are linked against glibc; for older systems build from source or use musl |
The pre-built binary is statically linked against V8 and the bundled C++ runtime; it does not depend on any system JS engine.
3.2 Build requirements (building from source)
| Tool | Why |
|---|---|
Rust toolchain pinned in rust-toolchain.toml |
The workspace requires a specific channel |
| Python 3 | V8 build scripts |
| C++ toolchain (clang preferred; MSVC on Windows; gcc supported) | Compiles V8 |
| protoc | Some workspace crates use Protocol Buffers |
| Git | Source control; builds use git revision metadata |
| ~10 GB free disk | V8 build artefacts |
| 8 GB+ RAM | Linker memory pressure on V8 |
V8 itself may compile for 30+ minutes on first build. Subsequent incremental builds are minutes.
3.3 Platform targets supported by deno compile
| Target | OS / arch |
|---|---|
x86_64-unknown-linux-gnu |
Linux x86_64 |
aarch64-unknown-linux-gnu |
Linux ARM64 |
x86_64-pc-windows-msvc |
Windows x86_64 |
x86_64-apple-darwin |
macOS Intel |
aarch64-apple-darwin |
macOS Apple Silicon |
Cross-compile by passing --target to deno compile. The denort runtime for the target is downloaded and cached automatically on first use.
4. Configuration Mastery
4.1 Configuration Discovery Order
Deno resolves configuration in this order (highest to lowest precedence):
- Command-line flags (
--config,--lock,--import-map,--allow-*). - Environment variables (
DENO_DIR,DENO_NO_PACKAGE_JSON, etc.). - Per-task
--env-fileif specified. deno.json/deno.jsoncdiscovered by walking from--initial-cwd(or the current working directory) upward.package.jsondiscovered the same way (when not disabled byDENO_NO_PACKAGE_JSON).- Built-in defaults.
Use --no-config to disable deno.json discovery entirely; --config=path to pin a specific file.
Important
In workspace setups, each member's
deno.jsoninherits the rootimportsmap but can override individual specifiers. Task definitions do not inherit — each member must declare its own tasks.
4.2 The deno.json Schema
The full JSON schema is cli/schemas/config-file.v1.json (~600 lines). The top-level keys, exhaustively:
| Key | Type | Purpose |
|---|---|---|
name |
string | Package name (for JSR publish) |
version |
string | Package version (semver) |
exports |
string | object | Module entry point(s) |
imports |
object | Bare specifier → URL/spec mapping |
scopes |
object | Per-prefix import map overrides |
tasks |
object | Named scripts for deno task |
fmt |
object | Formatter settings |
lint |
object | Linter settings |
test |
object | Test discovery and runner settings |
bench |
object | Benchmark discovery and runner settings |
compilerOptions |
object | TypeScript compiler options (subset of tsc) |
compile |
object | deno compile defaults: include, exclude, permissions |
permissions |
object | Named permissionSets reusable across commands |
allowScripts |
boolean | array | object | npm lifecycle script policy |
nodeModulesDir |
"auto" | "manual" | "none" | node_modules/ strategy |
unstable |
array of strings | Enable unstable feature flags |
workspace |
array of strings | Member directories for monorepos |
lock |
string | boolean | Lockfile path or false to disable |
vendor |
string | Vendor directory for deno vendor |
license |
string | SPDX license identifier |
publish |
object | include / exclude for JSR publish |
links |
array | Local development links (workspace siblings via path) |
permissionSet schema (excerpt)
"permissionSet": {
"all": boolean,
"read": true | false | string[] | { allow: ..., deny: ..., ignore: ... },
"write": true | false | string[] | { allow: ..., deny: ... },
"import": true | false | string[] | { allow: ..., deny: ... },
"env": true | false | string[] | { allow: ..., deny: ..., ignore: ... },
"net": true | false | string[] | { allow: ..., deny: ... },
"run": true | false | string[] | { allow: ..., deny: ... },
"ffi": true | false | string[] | { allow: ..., deny: ... },
"sys": true | false | string[] | { allow: ..., deny: ... }
}
The asymmetry — only read, env, sys accept the ignore key — reflects which categories support being silently no-op'd vs. denied.
4.3 Workspace (Monorepo) Configuration
A root deno.json declares workspace:
{ "workspace": ["./packages/core", "./packages/cli", "./packages/web"] }
Each member has its own deno.json with name / version / exports. Commands operate over the full workspace:
| Behavior | Detail |
|---|---|
deno test |
Runs tests in every member |
deno fmt / deno lint |
Operates on every member |
deno publish |
Publishes every member to JSR |
--filter <pattern> |
Restrict to matching member names |
imports resolution |
Workspace members resolve each other by their name automatically |
libs/config/workspace implements discovery and member resolution.
4.4 Lockfile (deno.lock)
libs/lockfile defines the format. Top-level keys:
| Key | Purpose |
|---|---|
version |
Lockfile schema version (currently 4) |
specifiers |
Map of jsr: / npm: specifier → resolved version |
jsr |
Per-package integrity hashes |
npm |
npm dependency graph with integrity, dist tarball URL, dependencies |
redirects |
Recorded HTTP redirects for stable resolution |
remote |
Integrity hashes for https:// imports |
workspace |
Per-workspace-member resolved deps |
Pass --frozen to fail when the lockfile would change (CI default for reproducible builds). --no-lock disables lockfile use entirely.
4.5 compilerOptions — TypeScript Settings
A subset of tsconfig.json. Notable supported keys:
strict, strictNullChecks, noImplicitAny, noFallthroughCasesInSwitch, noImplicitOverride, noImplicitThis, noUnusedLocals, noUnusedParameters, useDefineForClassFields, experimentalDecorators, emitDecoratorMetadata, lib, jsx, jsxFactory, jsxFragmentFactory, jsxImportSource, jsxImportSourceTypes, target, types, checkJs, allowJs.
Notably not supported: outDir, rootDir, module, moduleResolution, paths, baseUrl — Deno controls module resolution itself via imports / scopes.
compilerOptions.lib defaults are derived from the unstable flags. With no flags, ["deno.window"] is used. Adding "dom" enables browser globals; "deno.unstable" adds typings for unstable APIs.
4.6 Environment Variable Precedence
Some env vars have CLI flag equivalents. Where both are set:
| Env var | CLI override | Winner |
|---|---|---|
DENO_DIR |
none | env |
DENO_INSTALL_ROOT |
deno install --root=PATH |
CLI |
DENO_COVERAGE_DIR |
--coverage=DIR |
CLI |
DENO_NO_UPDATE_CHECK |
none | env |
DENO_AUTH_TOKENS |
none | env |
DENO_CERT |
none | env (combined with system store via DENO_TLS_CA_STORE) |
HTTP_PROXY / HTTPS_PROXY |
none | env |
NO_COLOR |
none | env |
FORCE_COLOR |
none | env (overrides NO_COLOR if both set) |
Full list of supported env vars: see User Guide Section 7 (sourced from cli/args/flags.rs:1452-1591).
5. Security
5.1 Threat Model
Deno's primary security posture is to make a script safe for the user to run, not to make the host machine safe for the user. Untrusted code runs inside the V8 sandbox and can only escape via Deno-controlled ops, all of which are gated by the permission system. Secondary properties:
- The V8 process is the security boundary; there is no in-process privilege separation.
- Permissions are checked at op-call time, not at module-load time. A malicious module loaded with
--allow-netcan still attempt FS reads — the check fires when it tries. - Permissions cannot be widened at runtime. They can only be narrowed via
--deny-*or viaDeno.permissions.revoke(). - The permission prompt is interactive and bypassable in CI by
--no-prompt.
5.2 Permission Model Internals
runtime/permissions/lib.rs defines PermissionsContainer, the central state for a worker. Each permission category is a struct (e.g. UnaryPermission<NetDescriptor>) holding allow-list, deny-list, ignore-list, and prompted state.
Op call flow:
- The op extracts
&PermissionsContainerfrom theOpState. - It calls a typed check method, e.g.
state.check_net(&host, port, "Deno.connect"). - The check evaluates: deny list → prompted-deny → granted-allow → allow-list → prompted-allow → prompt user (or deny if
--no-prompt). - On allow, the op proceeds; on deny, it returns
PermissionDeniedto JS.
is_allow_all() short-circuits to true when --allow-all was used. Without -A, every op call goes through the full check.
Warning
Permissions are checked at op-call time, not at module-load time. A module imported with
--allow-netcan still attempt file system reads — the denial fires only when the syscall-level op is invoked. Always apply the principle of least privilege.
5.3 Known Security Considerations
| Area | Note |
|---|---|
--allow-import |
Required to fetch new remote modules. Without it, only cached modules can be imported. This is a meaningful supply-chain control. |
--allow-net=* / bare --allow-net |
Grants all hosts. Always pass an allow-list in production. |
Permission grants in deno compile |
Baked into the binary; cannot be widened. Reduces attack surface for shipped binaries. |
| npm lifecycle scripts | Disabled by default (allowScripts: false). Approve explicitly via deno approve-scripts or deno install --allow-scripts=npm:foo. |
FFI (--allow-ffi) |
Loads arbitrary dynamic libraries; equivalent to running native code. Treat as --allow-all. |
Subprocess (--allow-run) |
Spawned children inherit no Deno permissions but full OS capabilities of the parent process. The child is outside Deno's sandbox. |
| HTTPS module integrity | Lockfile records hashes; --frozen fails on mismatch. |
DENO_TRACE_PERMISSIONS=1 |
Adds stack traces to prompts. Useful for debugging supply-chain issues. |
5.4 Hardening Recommendations
- Use
--no-promptin CI and in production binaries to convert silent denials into errors. - Use
--allow-net=with explicit hosts; never bare--allow-net. - Use
--allow-read=and--allow-write=with explicit paths. - Set
unstableonly to the features actually needed; do not blanket-enable. - Pin Deno versions in CI (
actions/setup-deno@vNwithdeno-version: vX.Y.Z). - Commit
deno.lockand rundeno install --frozen(or rely on the--frozendefault in CI mode). - Use
DENO_NO_UPDATE_CHECK=1in air-gapped environments. - For network apps, run behind a reverse proxy and set
DENO_TRUST_PROXY_HEADERS=1only when the proxy is trusted to set the client address. - Approve npm lifecycle scripts package-by-package, never globally.
Caution
--allow-ffiloads arbitrary native dynamic libraries, effectively granting the same capabilities as--allow-all. Treat any FFI usage as a full trust boundary escape.
6. Operations and Maintenance
6.1 Cache Layout ($DENO_DIR)
Default path: $HOME/.cache/deno (Linux), $HOME/Library/Caches/deno (macOS), %LOCALAPPDATA%\deno (Windows). Override with DENO_DIR.
$DENO_DIR/
deps/ HTTPS module cache (URL → file)
npm/ npm package cache (registry → tarball)
registries/ jsr:/npm: metadata cache
gen/ Transpiled JS for TS modules (keyed by source hash)
check/ Type-check artefacts and diagnostics cache
emit/ Emit cache (build outputs)
binary/ Downloaded denort runtimes for `deno compile --target`
binary_versions.json Tracker for downloaded denort versions
kv/ Default storage for Deno.openKv() (SQLite files)
webcache/ Web Cache API storage
webstorage/ localStorage / sessionStorage SQLite files
repl.log REPL history (override with DENO_REPL_HISTORY)
upgrade_check/ Last-update-check timestamp
deno clean removes the entire $DENO_DIR. To selectively reload, use --reload[=URL].
Tip
Set
DENO_DIRto a shared network path for team-wide cache sharing. The cache is content-addressed and safe for concurrent access from multiple Deno processes.
6.2 Logging
Deno respects RUST_LOG (Rust env_logger syntax):
RUST_LOG=info deno run main.ts
RUST_LOG=deno=debug,deno_runtime=trace deno run main.ts
RUST_LOG=deno_core::module_loader=trace deno run main.ts
Useful target prefixes: deno, deno_runtime, deno_core, deno_npm, deno_resolver, deno_lsp, tokio. Combine with DENO_TRACE_PERMISSIONS=1 to debug permission issues.
6.3 Monitoring (OpenTelemetry)
ext/telemetry exposes the runtime via OpenTelemetry. Configure with standard OTel env vars:
| Var | Purpose |
|---|---|
OTEL_DENO_TRACING |
true/false to toggle tracing |
OTEL_DENO_METRICS |
true/false to toggle metrics |
OTEL_DENO_CONSOLE |
ignore / capture / replace — controls console.* trace export |
DENO_UNSTABLE_OTEL_DETERMINISTIC |
Integer seed for deterministic span IDs (testing) |
Standard OTEL_EXPORTER_OTLP_* |
Endpoint, headers, etc. |
The runtime exports HTTP request spans, op spans, and JS-emitted spans (when user code uses @opentelemetry/api).
Note
OpenTelemetry support requires
--unstable-otelor adding"otel"to theunstablearray indeno.json. Without this flag, theOTEL_*environment variables are ignored.
6.4 Inspector and Debugging
deno run --inspect main.ts # listen on 127.0.0.1:9229
deno run --inspect-brk main.ts # break before user code
deno run --inspect-wait main.ts # wait for debugger to attach
deno run --inspect=0.0.0.0:9229 main.ts # listen on all interfaces (BE CAREFUL)
Open chrome://inspect in Chrome to attach. The inspector speaks the standard V8 Debugger Protocol.
The standalone inspector server (libs/inspector_server) is also used by deno lsp for IDE debugging integration.
Warning
Never expose the inspector on
0.0.0.0in production. The V8 inspector protocol allows arbitrary code execution — an attacker with network access to the inspector port gains full control of the process.
6.5 Upgrade and Rollback
deno upgrade # latest stable
deno upgrade canary # canary
deno upgrade <version> # specific version
deno upgrade --output PATH # specify output binary path
deno upgrade --dry-run # show what would happen, don't change anything
deno upgrade --force # overwrite when target version equals current
The upgrade subcommand downloads from https://github.com/denoland/deno/releases, verifies the SHA256, and atomically replaces the binary. To roll back to a known version: deno upgrade <previous-version>. There is no built-in version manager; for multi-version coexistence use a tool like dvm or install side-by-side via different DENO_INSTALL_ROOT directories.
6.6 Cron and Long-Running Servers
For production HTTP servers using Deno.serve:
- Run with
--parallelto spawn one worker per CPU. - Use a process supervisor (systemd, supervisord, Docker restart policies) — Deno does not daemonize.
- For Linux, set
DENO_USR2_MEMORY_TRIM=1to allow SIGUSR2 to free memory. - Use
DENO_USE_CGROUPS=1so V8's heap limit reflects container memory limits, not host RAM.
For scheduled work: Deno.cron("name", "* * * * *", handler) (requires --unstable-cron). State for cron is stored in Deno KV.
6.7 Backup
| Item | Backup target |
|---|---|
| Deno KV data | $DENO_DIR/kv/*.db (SQLite) — copy with KV closed, or use SQLite .backup |
| Web storage | $DENO_DIR/webstorage/ |
| Web cache | $DENO_DIR/webcache/ |
deno.lock |
Always commit to source control |
The dependency cache (deps/, npm/) is reproducible from deno.lock + network and does not need backup.
6.8 Deployment Topologies
Single-process: a deno serve instance behind nginx/Caddy. Set --port, --host=127.0.0.1, and proxy.
Multi-process: spawn N instances bound to different ports, load-balance with the proxy. Deno does not provide cluster-mode; use OS-level process management.
Single-binary deployment: deno compile -A --output app main.ts produces a self-contained executable. Ship it directly. The binary embeds the runtime, so the target machine needs no Deno installation.
Container: denoland/deno:latest is the official image. For minimal images, use gcr.io/distroless/cc and copy a deno compiled binary in.
7. Build and Distribution
7.1 Cargo Workspace
Root Cargo.toml declares the workspace with resolver = "2" and lists all 70+ members. Workspace-level package metadata (authors, edition, license, repository) is inherited by all crates. Workspace-level dependencies pin every external dependency version once and inject path = "./..." for internal crates so they are always built from source.
Build the binary:
cargo build --release -p deno
./target/release/deno --version
Build with debug symbols and dhat heap profiling:
cargo build --profile=release-with-debug --features=dhat-heap -p deno
Build the slim runtime crate alone (for embedding):
cargo build -p deno_runtime
7.2 Cargo Features (cli crate)
| Feature | Default | Purpose |
|---|---|---|
upgrade |
yes | Enables deno upgrade and the background update check. Disabled by Linux distros that ship Deno as a system package. |
__vendored_zlib_ng |
yes | Vendor zlib as zlib-ng for performance. |
dhat-heap |
no | Enables dhat heap profiling. |
hmr |
no | Disables snapshot loading; re-evaluates JS sources on every run for live editing. |
lsp-tracing |
no | Adds tracing/OpenTelemetry instrumentation to deno lsp. |
panic-trace |
no | Custom panic backtrace via deno_panic. |
7.3 Build-Time Snapshot
cli/build.rs builds a V8 startup snapshot. The snapshot embeds:
deno_coreinfrastructure JS- All
ext/*JS bootstrap code (eachExtensioncontributes itsesmfiles at snapshot time) runtime/js/99_main.js— the Deno namespace setup
Without snapshotting, startup parses ~2 MB of JS on every invocation. With it, V8 mmap-loads a binary blob and starts in milliseconds.
cli/snapshot/ holds the persisted snapshot artifacts pulled into the binary by include_bytes!.
7.4 Release Process
- Tag a commit with
vX.Y.Zonmain. - CI (GitHub Actions) builds binaries for all 5 target triples.
- Each binary is signed and uploaded to a GitHub Release.
- The install scripts (
install.sh,install.ps1) detect the latest release and download the appropriate binary. deno upgradequeries the same GitHub Releases API.
Releases.md in the repo root is the canonical changelog.
7.5 Dependency Management for the Workspace
External dependencies are pinned in the root [workspace.dependencies] block. Internal deno crates use path references so workspace builds always use local source. Deno-specific helper crates (deno_ast, deno_lockfile, deno_doc, deno_lint, etc.) are pinned to exact versions via =X.Y.Z.
To upgrade an internal crate: bump the =X.Y.Z line in [workspace.dependencies], run cargo update -p <crate>, and verify with cargo check --workspace.
7.6 Cross-Compile
Cargo cross-compile setup is in .cargo/config.toml. CI uses cross and platform-specific runners (Apple Silicon and ARM Linux are native; Windows is MSVC).
Cross-compile a deno compile binary (different from cross-compiling deno itself):
deno compile --target aarch64-unknown-linux-gnu --output myapp-arm64 main.ts
The denort runtime for the target architecture is downloaded to $DENO_DIR/binary/ on first cross-compile.
8. Module Reference
8.1 The deno_core Trinity
deno_core is the foundation. The three central types:
JsRuntime
libs/core/runtime/jsruntime.rs. Owns a V8 Isolate and Context. Provides:
new(options)— construct a runtime with extensions, snapshot, module loader, op state seed.execute_script(name, source)— synchronously execute JS, returns the V8 value.load_main_module(specifier, code)— load a module graph rooted atspecifier.mod_evaluate(module_id)— evaluate a loaded module; returns a future for the result.run_event_loop(opts)— drive Tokio + microtasks until idle.
Extension
libs/core/extensions.rs. Bundles ops, ESM modules, and state seed for a feature. Built via the extension! macro:
extension!(deno_my,
deps = [deno_web, deno_url],
ops = [op_foo, op_bar],
esm_entry_point = "ext:deno_my/01_main.js",
esm = ["01_main.js", "02_helpers.js"],
options = { foo: Arc<dyn FooSrv> },
);
deps enforces extension-load ordering. options are passed at runtime via MyExtensionOptions { ... }.
ModuleLoader
Trait in libs/core/modules/. Implementors handle resolve() (specifier → URL) and load() (URL → source). The CLI's loader at cli/module_loader.rs handles HTTPS, JSR, npm, file, data, and node schemes.
8.2 The op2 Macro
libs/ops/op2/. The #[op2] proc-macro generates V8 fast-call entry points. Variants:
| Form | Purpose |
|---|---|
#[op2] |
Sync op |
#[op2(async)] |
Async op (returns Future) |
#[op2(fast)] |
Force fast-call code path (sync only, restricted parameter types) |
#[op2(reentrant)] |
Allows re-entering JS from within the op (rare) |
#[op2(stack_trace)] |
Capture JS stack trace at call site (perf cost) |
#[op2(no_side_effects)] |
Hint to V8 for further optimization |
Parameter attributes:
| Attr | Meaning |
|---|---|
#[string] |
Cheap string conversion |
#[buffer] |
Treat as Uint8Array/Buffer, no copy |
#[serde] |
Use serde_v8 for serialization |
#[state] |
Inject from OpState |
#[smi] |
Small integer fast path |
#[bigint] |
BigInt as i64/u64 |
8.3 Module Graph and Resolution
The graph is built by deno_graph (an external crate, but wholly authored by the Deno team). It traverses imports starting from the entry point, applies the import map, resolves npm/jsr specifiers via the local resolver, and produces a module graph used for type-checking, bundling, vendoring, publishing, and execution.
Key types:
| Type | Purpose |
|---|---|
ModuleGraph |
The full graph: nodes (modules), edges (imports), deps, redirects. |
ModuleSpecifier |
A normalized URL (url::Url). |
WorkspaceResolver |
Resolves bare specifiers within a workspace. |
NpmResolver |
npm version resolution + node_modules layout. |
JsrResolver |
JSR resolution + version selection. |
8.4 The Type-Checking Pipeline
cli/type_checker.rs and cli/tsc/. As of v2, Deno uses TypeScript-Go (a Go port of tsc) for type-checking. The Rust client (libs/typescript_go_client/) talks to the Go process over IPC.
Flow:
deno check main.tsbuilds the module graph.- The graph is serialized and sent to the TS-Go subprocess.
- TS-Go runs the type checker and returns diagnostics.
- Diagnostics are formatted and printed by
deno_runtime::fmt_errors. - Successful type-check results are cached in
$DENO_DIR/check/.
The legacy tsc integration (in cli/tsc/) remains for cases where TS-Go cannot handle the graph (some experimental syntax, compilerOptions.experimentalDecorators interactions). Both paths produce the same diagnostic format.
8.5 The Language Server (deno lsp)
cli/lsp/. A Tower-LSP server that implements the standard LSP plus Deno-specific extensions:
| Custom request | Purpose |
|---|---|
deno/cache |
Cache deps for the workspace |
deno/performance |
Performance metrics |
deno/reloadImportRegistries |
Refresh JSR/npm completion data |
deno/task |
List runnable tasks |
deno/testing/* |
Test discovery, run requests, results |
deno/virtualTextDocument |
Read transient documents (e.g. opened from a URL) |
The LSP shares the resolver, type-checker, and module loader with the rest of the CLI; opening a workspace in an editor exercises the same resolution path as deno run.
8.6 The Standalone Runtime (denort)
cli/rt/. A reduced binary that ships inside deno compile outputs. It contains the runtime + ext extensions but omits the type checker, formatter, linter, doc generator, REPL, and LSP. The eszip appended to the executable contains the user's bundled module graph; on startup, denort loads the eszip into memory and executes the entry point.
ext/rt_helper provides shared helpers between cli/ and cli/rt/.
8.7 The Bundler
ext/bundle + cli/tools/bundle/. The bundler is built on esbuild (via WASM). It produces ESM bundles for browser/Deno targets and is the engine behind:
| Use | Mechanism |
|---|---|
deno bundle main.ts |
Direct user invocation |
deno compile |
Bundler produces the eszip embedded in the binary |
Deno.bundle(...) |
Programmatic API (unstable) |
8.8 The Package Manager (cli/tools/pm)
cli/tools/pm/. Implements deno add, deno remove, deno install, deno uninstall, deno outdated, deno update, and deno approve-scripts. Backed by:
libs/npm— npm package resolutionlibs/npm_cache— disk cachelibs/npm_installer— node_modules layoutlibs/npmrc— registry/auth configlibs/resolver/npm— runtime npm resolution
9. Code Patterns
9.1 Cargo Conventions
- Workspace-pinned everything. Every external dep is in
[workspace.dependencies]. Crates useworkspace = true. - Per-crate features for slim builds.
deno_runtimehastranspile,snapshot,hmrfeatures so embedders can opt in/out of features that pull in heavy deps. - Internal crates ship to crates.io.
deno_core,deno_ast,deno_lockfile,deno_doc,deno_lint, etc. are independently versioned and published.
9.2 Async Patterns
- The runtime is single-threaded Tokio (
current_threadflavor) — V8 isolates are not Send. WebWorkerspawns a fresh OS thread with its own Tokio runtime.- Long-running ops yield via
tokio::spawn_blockingfor FS-heavy work; otherwise they run on the main thread. - Cancellation is cooperative via
CancelHandle(adeno_coretype).
9.3 Error Handling
deno_error::JsErrorBox and AnyError are the universal error types crossing the JS/Rust boundary. Errors implement JsError for proper translation to JS exception classes (TypeError, RangeError, Deno.errors.PermissionDenied, etc.). Most ops return Result<T, AnyError>.
9.4 Snapshot-Friendly Code
Anything in an ext/ JS file that runs at extension load runs at snapshot time (during the build), not at runtime. This means:
- No I/O, no Tokio.
- No date/time captures (would freeze build time into the snapshot).
- No network access —
URLs constructed at snapshot time embed the build host's machine state.
Code that must run per-process (e.g. reading env vars, getting Date.now()) goes in the 99_main.js bootstrap or in lazily-evaluated code paths.
9.5 State Management
OpState is a per-JsRuntime typemap (keyed by Rust type) that ops draw from. Idiomatic pattern: store an Arc<MyService> in OpState, pull it via #[state] in op signatures.
PermissionsContainer lives in OpState and is mutated only by the runtime (not by ops).
10. Testing Strategy
10.1 Test Categories
| Type | Where | Run with |
|---|---|---|
| Rust unit tests | cli/, runtime/, ext/*, libs/* |
cargo test --workspace |
| Spec-based integration tests | tests/specs/*/__test__.jsonc |
cargo test --test integration -- specs:: |
| JS unit tests | tests/unit/* (run inside Deno) |
tools/test.ts |
| Node compatibility tests | tests/unit_node/*, tests/node_compat/* |
tools/node_compat/run_all_test_unmodified.ts |
| WPT (Web Platform Tests) | tests/wpt/ |
tools/wpt.ts |
| FFI / NAPI / SQLite extension tests | tests/ffi, tests/napi, tests/sqlite_extension |
Compiled and exercised by spec tests |
| Benchmarks | tests/bench/ |
cargo bench or deno bench |
The spec format: each test directory has a __test__.jsonc describing args, env, expected exit code, expected stdout pattern, expected stderr pattern. The integration test runner discovers them recursively.
10.2 Local Mock Registries
tests/registry/ ships a self-contained JSR-shaped and npm-shaped registry served by the tests/util/server HTTP server during tests. This allows tests to reference deterministic packages like npm:@denotest/cjs-multiple-exports without hitting npm.org.
10.3 Adding a Spec Test
-
Create
tests/specs/<group>/<test_name>/. -
Add
__test__.jsonc:{ "args": "run -A main.ts", "output": "main.out" } -
Add
main.tsandmain.out(the expected stdout/stderr). -
Run
cargo test --test integration -- specs::<group>::<test_name>.
11. Operating in Production
11.1 Container Image
FROM denoland/deno:2.7.12 AS build
WORKDIR /app
COPY deno.json deno.lock ./
RUN deno install --frozen
COPY . .
RUN deno cache --frozen main.ts
CMD ["deno", "run", "--no-prompt", "--allow-net", "--allow-read=./data", "main.ts"]
For minimal image size, use deno compile and a distroless base:
FROM denoland/deno:2.7.12 AS build
WORKDIR /app
COPY . .
RUN deno compile --output myapp -A main.ts
FROM gcr.io/distroless/cc
COPY --from=build /app/myapp /usr/local/bin/myapp
ENTRYPOINT ["/usr/local/bin/myapp"]
11.2 systemd Unit
[Unit]
Description=My Deno Service
After=network.target
[Service]
ExecStart=/usr/local/bin/deno run --no-prompt --allow-net --allow-read=/var/lib/myapp /var/lib/myapp/main.ts
Restart=on-failure
RestartSec=5
User=myapp
Environment=DENO_DIR=/var/cache/deno
Environment=DENO_NO_UPDATE_CHECK=1
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
11.3 Kubernetes
Run deno serve --parallel --host 0.0.0.0 --port 8080 main.ts in the container. Expose port 8080. The pod's CPU limit governs the parallel worker count when DENO_USE_CGROUPS=1 is set.
For graceful shutdown: Deno.serve already handles SIGTERM. Kubernetes' default terminationGracePeriodSeconds: 30 is sufficient for most workloads.
11.4 Resource Limits
| Setting | How |
|---|---|
| V8 heap limit | --v8-flags=--max-old-space-size=4096 (MB) |
| File descriptor limit | OS-level (ulimit -n or systemd LimitNOFILE) |
| CPU pinning | OS-level (taskset, cgroups, k8s cpu limits) |
| Memory pressure response | DENO_USR2_MEMORY_TRIM=1 + kill -USR2 <pid> to free memory (Linux only) |
12. Troubleshooting (Operator-Focused)
For developer-side troubleshooting, see the User Guide. This section covers operational issues.
Warning
Production Deno processes should always run with
--no-promptto convert interactive permission prompts into hard denials. An unattended process that blocks on a prompt will stall indefinitely.
Issue: Deno binary updates itself unexpectedly in a Linux distro package
Cause:
deno upgradeoverwrites the binary. The distro tracks a different version. Solution: Build withcargo build --release --no-default-features --features __vendored_zlib_ng -p denoto disable theupgradefeature.
Issue: Type-check is slow in CI
Cause: Cold cache; TypeScript-Go starting up; remote modules being fetched. Solution: Cache
$DENO_DIR/check/and$DENO_DIR/deps/between CI runs. Usedeno cache main.tsas a separate step beforedeno test.
Issue: V8 process exceeds container memory
Cause: Default heap limit ignores cgroups. Solution: Set
DENO_USE_CGROUPS=1so V8 sees the container limit. Alternatively, pass--v8-flags=--max-old-space-size=<MB>.
Issue: deno compile binary fails on target with "GLIBC_X.YZ not found"
Cause: The pre-built
denortfor Linux is built against newer glibc than the target. Solution: Builddenortfrom source on a matching glibc base, or target a musl variant via cross-compilation.
Issue: Module fetch fails behind corporate proxy
Cause: Proxy credentials or CA cert. Solution: Set
HTTPS_PROXYandHTTP_PROXY. For private CA:DENO_CERT=/path/to/ca-bundle.pemand possiblyDENO_TLS_CA_STORE=system,mozilla.
Issue: deno serve --parallel only spawns one worker
Cause:
--parallelwithout an integer defaults to detected CPU count from cgroups; a misconfigured cgroup may report 1. Solution: Pass an explicit count:--parallel=8. Verify cgroup withnprocinside the container.
Issue: Inspector is reachable from outside the container
Cause:
--inspect=0.0.0.0:9229was used. Deno's inspector has no auth. Solution: Always use--inspect=127.0.0.1:9229and tunnel via SSH or kubectl port-forward. Never expose the inspector publicly.
Tip
When debugging CI cache misses, run
deno info --json main.tsto dump the full module graph with resolved URLs and cache paths. Compare against the previous run to identify what changed.
Issue: KV database corruption after kill -9
Cause: SQLite WAL not checkpointed. Solution: Use
kill -TERMand let Deno's signal handler close KV cleanly. For recovery, open the SQLite file withsqlite3and runPRAGMA wal_checkpoint(TRUNCATE);.
Issue: Lockfile drift across team members
Cause: Different platforms resolve packages slightly differently (npm optional deps). Solution: Commit the lockfile as-is and use
--frozenin CI. For platform-specific optional deps, usenpmrcsettings.
Issue: deno upgrade fails with "could not download release"
Cause: GitHub rate-limited the unauthenticated request, or the network blocks
github.com. Solution: SetGH_TOKEN(orGITHUB_TOKEN) for an authenticated request. For air-gapped environments, mirror releases internally and use--canary-url=...ordeno upgrade --output PATHafter manual download.
13. Contributing
13.1 Where to Start
- Code of conduct: .github/CODE_OF_CONDUCT.md
- Contributing guide: .github/CONTRIBUTING.md
- Good first issues: https://github.com/denoland/deno/labels/good first issue
13.2 Local Development
git clone https://github.com/denoland/deno.git
cd deno
cargo build -p deno # debug build (~5 min after V8 caches)
./target/debug/deno --version
cargo test -p deno_runtime # unit tests for runtime crate
cargo test --test integration -- specs::run:: # integration specs for `run`
For tight iteration on JS bootstrap code, use the hmr Cargo feature: cargo build --features hmr -p deno. With HMR, edits to runtime/js/* and ext/*/*.js take effect on the next deno run without rebuilding the snapshot.
13.3 Style and Lint
- Rust:
cargo fmt --checkandcargo clippy -- -D warnings. CI fails on either. - JS/TS:
deno fmtanddeno lint. CI runs both. - Spec tests: copyright header is mandatory (
// Copyright 2018-2026 the Deno authors. MIT license.).
13.4 PR Process
- Branch from
main. - Open a PR with a clear description and (where relevant) a spec test.
- CI runs the full matrix: build on all 5 targets + spec tests + WPT subset + lint + format.
- Maintainers review. Most PRs require one approval; substantial changes require two.
- Squash-merge into
mainwith a Conventional Commits-style message.
13.5 Architecture for New Contributors
| Want to add | Start here |
|---|---|
A new Deno.* API |
ext/<closest_existing>/lib.rs + JS in same crate. Add ops with #[op2]. Wire from runtime/lib.rs. |
| A new CLI subcommand | cli/args/flags.rs (clap definition + parse) + cli/tools/<cmd>.rs (handler). |
| A new node:* polyfill | ext/node/polyfills/<module>.ts. Wire in ext/node/polyfill_proxy.ts. |
| A new lint rule | deno_lint external crate (https://github.com/denoland/deno_lint). |
| A new lockfile field | libs/lockfile/lib.rs. Bump schema version if breaking. |
14. Quick Reference
14.1 Rebuild After Pulling
cargo build -p deno --release
14.2 Run Specific Spec Test
cargo test --test integration -- specs::run::specific_test_name
14.3 Re-Generate the JSON Schema for deno.json
The schema lives at cli/schemas/config-file.v1.json and is hand-maintained. Update it whenever a new top-level key is added.
14.4 Useful Files
| Concern | Path |
|---|---|
| All CLI flags and subcommands | cli/args/flags.rs |
| All env vars Deno reads | cli/args/flags.rs:1452-1591 (ENV_VARS) |
| Permission model | runtime/permissions/lib.rs |
| Module loader | cli/module_loader.rs |
deno.json schema |
cli/schemas/config-file.v1.json |
| Lockfile format | libs/lockfile/ |
| ext extension contract | libs/core/extensions.rs |
| op2 macro | libs/ops/op2/ |
| Workspace member list | Cargo.toml (root) |
14.5 GitHub Issues Referenced in Troubleshooting
| Issue | Topic |
|---|---|
| #33291 | crypto.verify Invalid PEM |
| #33296 | TLS to Postgres via npm:pg@8 times out |
| #33254 | Windows writeFileSync EEXIST |
| #32821 | deno compile binary hang on 2.7.5+ |
| #33288 | node:http Server vs node:net Server inheritance |
| #33284 | LSP Test Explorer divergence with --env-file |
| #33289 | LSP global types not recognized until file is opened |