# Extension points — denoland/deno

> Canonical page: https://codevetter.com/unpack/deno/extension-points

# Extension points — denoland/deno

Scanned commit 461f32ec6174d88b7e43654a054d46d67ae2fdab (2026-09-21). Generated by codevetter codevetter 1.12.0 on 2026-09-22.

New native capability is added as an `ext/<name>` crate using `deno_core::extension!` (ops + esm + lazy_loaded_esm), registered into the ordered list in `runtime/worker.rs::common_extensions`, and exposed to JS from the extension's own `.js`/`.ts` files. New CLI surface is added by extending the `const` tables in `libs/cli_parser/src/defs.rs`, adding a `DenoSubcommand` variant in `flags.rs`, a conversion in `convert.rs`, an arm in `run_subcommand`, and a handler module under `cli/tools/`. New shared services are added as `Deferred<T>` fields on `CliFactoryServices` with an accessor method. The `deno task` shell has its own `ShellCommand` registry, the module loader has a JS-level load-hook registry, and the developer tooling itself is extensible via `tools/x.ts`'s command record and `.claude/skills/`.
- An extension is declared with the `deno_core::extension!` macro taking `deps`, `parameters`, `ops = [...]`, `esm = [...]`, `lazy_loaded_esm = [...]`, and `synthetic_esm = [...]` — `ext/node/lib.rs:201` is the most elaborate example, parameterised over `InNpmPackageChecker`, `NpmPackageFolderResolver`, and `ExtNodeSys`. — [ext/node/lib.rs#L201-230](https://github.com/denoland/deno/blob/461f32ec6174d88b7e43654a054d46d67ae2fdab/ext/node/lib.rs#L201-L230) [ext/node/lib.rs#L468-530](https://github.com/denoland/deno/blob/461f32ec6174d88b7e43654a054d46d67ae2fdab/ext/node/lib.rs#L468-L530)
- Ops are written with the `#[op2]` proc macro defined at `libs/ops/lib.rs:25`, with the codegen split across `libs/ops/op2/`, `conversion/`, `cppgc.rs`, and `webidl/`, and a dedicated `libs/ops/compile_test_runner` crate that asserts macro expansion still compiles. — [libs/ops/lib.rs#L25-27](https://github.com/denoland/deno/blob/461f32ec6174d88b7e43654a054d46d67ae2fdab/libs/ops/lib.rs#L25-L27) [libs/ops/op2](https://github.com/denoland/deno/blob/461f32ec6174d88b7e43654a054d46d67ae2fdab/libs/ops/op2) [Cargo.toml#L50-52](https://github.com/denoland/deno/blob/461f32ec6174d88b7e43654a054d46d67ae2fdab/Cargo.toml#L50-L52)
- Registering a new extension means adding one line to the `vec![]` in `common_extensions` at the correct position; `.disable()` is available to register ops without activating them, as done for `ops::web_worker::deno_web_worker` in the main worker. — [runtime/worker.rs#L1219-1262](https://github.com/denoland/deno/blob/461f32ec6174d88b7e43654a054d46d67ae2fdab/runtime/worker.rs#L1219-L1262)
- Adding a CLI subcommand is a five-file change: a `DenoSubcommand` variant + flags struct in `libs/cli_parser/src/flags.rs`, a `CommandDef` in `defs.rs`, a converter in `convert.rs`, an arm in `cli/lib.rs::run_subcommand`, and a handler under `cli/tools/`. — [libs/cli_parser/src/flags.rs#L786-833](https://github.com/denoland/deno/blob/461f32ec6174d88b7e43654a054d46d67ae2fdab/libs/cli_parser/src/flags.rs#L786-L833) [libs/cli_parser/src/defs.rs#L1-30](https://github.com/denoland/deno/blob/461f32ec6174d88b7e43654a054d46d67ae2fdab/libs/cli_parser/src/defs.rs#L1-L30) [cli/lib.rs#L124-556](https://github.com/denoland/deno/blob/461f32ec6174d88b7e43654a054d46d67ae2fdab/cli/lib.rs#L124-L556) [cli/tools/mod.rs](https://github.com/denoland/deno/blob/461f32ec6174d88b7e43654a054d46d67ae2fdab/cli/tools/mod.rs)
- New shared services plug into `CliFactoryServices` as a `Deferred<T>` field plus a `pub fn` accessor using `get_or_try_init` / `get_or_try_init_async`; the async variant exists specifically because some initialisation futures were large enough to overflow the Windows stack. — [cli/factory.rs#L269-300](https://github.com/denoland/deno/blob/461f32ec6174d88b7e43654a054d46d67ae2fdab/cli/factory.rs#L269-L300) [cli/factory.rs#L302-332](https://github.com/denoland/deno/blob/461f32ec6174d88b7e43654a054d46d67ae2fdab/cli/factory.rs#L302-L332)
- `deno task` exposes a shell-command registry: implementing `deno_task_shell::ShellCommand` and registering it (as `NpmCommand`, `NpxCommand`, `NodeCommand`, `NodeGypCommand`, `DenoCommand` do) intercepts that binary name inside task scripts. — [cli/task_runner.rs#L306-470](https://github.com/denoland/deno/blob/461f32ec6174d88b7e43654a054d46d67ae2fdab/cli/task_runner.rs#L306-L470) [cli/task_runner.rs#L178-230](https://github.com/denoland/deno/blob/461f32ec6174d88b7e43654a054d46d67ae2fdab/cli/task_runner.rs#L178-L230)
- The module loader exposes a JS-facing hook system: `ops::module_hooks::{op_module_hooks_register, op_module_hooks_poll_load, op_module_hooks_respond_load, op_module_default_resolve}` back a `LoaderHookRegistry` that `CliModuleLoader::load` consults before the graph, supporting Node-style `nextLoad(newUrl)` delegation. — [ext/node/lib.rs#L206-210](https://github.com/denoland/deno/blob/461f32ec6174d88b7e43654a054d46d67ae2fdab/ext/node/lib.rs#L206-L210) [cli/module_loader.rs#L1445-1520](https://github.com/denoland/deno/blob/461f32ec6174d88b7e43654a054d46d67ae2fdab/cli/module_loader.rs#L1445-L1520) [runtime/shared.rs#L110-120](https://github.com/denoland/deno/blob/461f32ec6174d88b7e43654a054d46d67ae2fdab/runtime/shared.rs#L110-L120)
- Alternative module sources plug in at `CliModuleLoaderFactory` construction: an `EszipModuleLoader` (from `eszip_module_loader_provider`) short-circuits `load()` entirely, which is how `deno run --eszip` works. — [cli/factory.rs#L1256-1282](https://github.com/denoland/deno/blob/461f32ec6174d88b7e43654a054d46d67ae2fdab/cli/factory.rs#L1256-L1282) [cli/module_loader.rs#L2224-2290](https://github.com/denoland/deno/blob/461f32ec6174d88b7e43654a054d46d67ae2fdab/cli/module_loader.rs#L2224-L2290) [cli/lib.rs#L326-334](https://github.com/denoland/deno/blob/461f32ec6174d88b7e43654a054d46d67ae2fdab/cli/lib.rs#L326-L334)
- CLI-only ops live in `cli/ops/` (`bench.rs`, `deploy.rs`, `jupyter.rs`, `lint.rs`, `testing.rs`) and are injected per-subcommand via the `custom_extensions: Vec<Extension>` parameter of `create_custom_worker` — the deploy path passes `ops::deploy::deno_deploy::init()`. — [cli/ops/mod.rs](https://github.com/denoland/deno/blob/461f32ec6174d88b7e43654a054d46d67ae2fdab/cli/ops/mod.rs) [cli/worker.rs#L596-610](https://github.com/denoland/deno/blob/461f32ec6174d88b7e43654a054d46d67ae2fdab/cli/worker.rs#L596-L610) [cli/tools/deploy.rs#L85-95](https://github.com/denoland/deno/blob/461f32ec6174d88b7e43654a054d46d67ae2fdab/cli/tools/deploy.rs#L85-L95)
- The developer CLI is itself a registry: `buildCommands(root)` in `tools/x.ts` returns a `Record<string, Command>` where each entry supplies `description`, a long `help` string explicitly written for LLM consumption, and an async `fn`; `cargoTestCommand` is a factory for the five test commands. — [tools/x.ts#L27-62](https://github.com/denoland/deno/blob/461f32ec6174d88b7e43654a054d46d67ae2fdab/tools/x.ts#L27-L62) [tools/x.ts#L102-395](https://github.com/denoland/deno/blob/461f32ec6174d88b7e43654a054d46d67ae2fdab/tools/x.ts#L102-L395)
- Agent workflows plug in via `.claude/skills/`: six skills (`fmt`, `lint-all`, `lint-js`, `node-compat`, `issue-triage`, `review-pr`) each declare frontmatter with `allowed-tools` — e.g. `review-pr` is scoped to `Bash(gh *) Bash(git *) Read Glob Grep Agent`. — [.claude/skills/review-pr/SKILL.md#L1-10](https://github.com/denoland/deno/blob/461f32ec6174d88b7e43654a054d46d67ae2fdab/.claude/skills/review-pr/SKILL.md#L1-L10) [.claude/skills/fmt/SKILL.md](https://github.com/denoland/deno/blob/461f32ec6174d88b7e43654a054d46d67ae2fdab/.claude/skills/fmt/SKILL.md)
- New CI jobs are added by editing the TypeScript generators (`.github/workflows/ci.ts` etc.) and regenerating; `job()`, `step()`, `defineMatrix()` and condition helpers like `hasPrLabel("ci-full")` compose the workflow, and lint fails if the `.generated.yml` is stale. — [.github/workflows/ci.ts#L33-100](https://github.com/denoland/deno/blob/461f32ec6174d88b7e43654a054d46d67ae2fdab/.github/workflows/ci.ts#L33-L100) [.github/workflows/ci.ts#L270-360](https://github.com/denoland/deno/blob/461f32ec6174d88b7e43654a054d46d67ae2fdab/.github/workflows/ci.ts#L270-L360) [tools/lint.js#L38](https://github.com/denoland/deno/blob/461f32ec6174d88b7e43654a054d46d67ae2fdab/tools/lint.js#L38)
- New integration coverage is added by dropping a directory with `__test__.jsonc` into `tests/specs/` — no Rust registration needed, since collection is directory-driven; `schema.json` documents the available keys (`args`, `output`, `envs`, `cwd`, `exitCode`, `flaky`, `if`, `tempDir`, `variants`, `repeat`, `timeout`). — [tests/specs/mod.rs#L290-305](https://github.com/denoland/deno/blob/461f32ec6174d88b7e43654a054d46d67ae2fdab/tests/specs/mod.rs#L290-L305) [tests/specs/schema.json#L1-70](https://github.com/denoland/deno/blob/461f32ec6174d88b7e43654a054d46d67ae2fdab/tests/specs/schema.json#L1-L70) [tests/specs/mod.rs#L44-110](https://github.com/denoland/deno/blob/461f32ec6174d88b7e43654a054d46d67ae2fdab/tests/specs/mod.rs#L44-L110)
- Lint rules can be added as JS plugins: `tools/lint_plugins/` is hashed into the unit-test CI hash because `lint_plugin_prefer_primordials_test` imports from it, and the runtime exposes `40_lint_selector.js`/`40_lint.js` to support plugin authoring. — [tests/unit/mod.rs#L24-27](https://github.com/denoland/deno/blob/461f32ec6174d88b7e43654a054d46d67ae2fdab/tests/unit/mod.rs#L24-L27) [cli/worker.rs#L682-691](https://github.com/denoland/deno/blob/461f32ec6174d88b7e43654a054d46d67ae2fdab/cli/worker.rs#L682-L691) [tools/lint_plugins](https://github.com/denoland/deno/blob/461f32ec6174d88b7e43654a054d46d67ae2fdab/tools/lint_plugins)
- The permission descriptor layer is pluggable at the container level: `RuntimePermissionDescriptorParser` is constructed once in `CliFactory::permission_desc_parser` and handed to every `PermissionsContainer`, so descriptor semantics are swappable without touching op sites. — [cli/factory.rs#L1136-1143](https://github.com/denoland/deno/blob/461f32ec6174d88b7e43654a054d46d67ae2fdab/cli/factory.rs#L1136-L1143) [runtime/permissions/runtime_descriptor_parser.rs](https://github.com/denoland/deno/blob/461f32ec6174d88b7e43654a054d46d67ae2fdab/runtime/permissions/runtime_descriptor_parser.rs)

Every claim links to its cited files at the pinned commit. Citations identify source evidence; they do not establish runtime correctness. Independent CodeVetter scan — not reviewed by upstream maintainers.

## Public product links

- [CodeVetter](https://codevetter.com/)
- [Download](https://codevetter.com/download)
- [Documentation](https://codevetter.com/docs/)
- [Source](https://github.com/Codevetter/codevetter)
