denoland/deno · error · Error
ERR_VM_MODULE_NOT_MODULE
ERR_VM_MODULE_NOT_MODULE
Error message
Provided module is not an instance of Module
What it means
Thrown by vm.Module#link() when a linker callback resolves with a value that is not a vm.SourceTextModule or vm.SyntheticModule instance. After awaiting all linker promises, the polyfill duck-checks each result with isModule() (a non-null object carrying the internal kWrap symbol, vm.js:554). Node's V8 binding raises the same code, so behavior matches node:vm.
Source
Thrown at ext/node/polyfills/vm.js:680
}
const specifiers = [];
const linkerPromises = [];
for (let i = 0; i < requests.length; i++) {
const { specifier, attributes } = requests[i];
ArrayPrototypePush(specifiers, specifier);
const p = PromiseResolve(
linker(specifier, this, { attributes, assert: attributes }),
);
ArrayPrototypePush(linkerPromises, p);
}
const resolvedModules = await SafePromiseAll(linkerPromises);
const wraps = [];
for (let i = 0; i < resolvedModules.length; i++) {
const m = resolvedModules[i];
if (!isModule(m)) {
throw new ERR_VM_MODULE_NOT_MODULE();
}
if (m.context !== this[kContext]) {
throw new ERR_VM_MODULE_DIFFERENT_CONTEXT();
}
ArrayPrototypePush(wraps, m[kWrap]);
}
// Record this module's resolutions before recursing so a dependency that
// imports back into this module finds it already in `visited`.
op_vm_module_link(this[kWrap], specifiers, wraps);
for (let i = 0; i < resolvedModules.length; i++) {
await resolvedModules[i][kLinkGraph](linker, visited);
}
}
evaluate(options = { __proto__: null }) {
try {View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Make the linker return a `new vm.SourceTextModule(loadedCode, { identifier: specifier })` or a `new vm.SyntheticModule([...], cb)` instance for every specifier it is given
- Check for a missing `return` in the linker arrow function — an implicit undefined is the most common cause
- For virtual/static dependencies, return a SyntheticModule that setExport()s the needed values
- Make sure only one copy of the node:vm polyfill creates modules (no duplicate vm module instances across realms/bundles)
Example fix
// before
mod.link(async (specifier) => {
const code = fs.readFileSync(resolve(dir, specifier)); // returns Buffer -> ERR_VM_MODULE_NOT_MODULE
});
// after
mod.link(async (specifier) => {
const code = fs.readFileSync(resolve(dir, specifier), 'utf8');
return new vm.SourceTextModule(code, { identifier: specifier });
}); Defensive patterns
Strategy: type-guard
Validate before calling
const m = await linker(spec, mod, attrs);
if (!(m instanceof vm.SourceTextModule || m instanceof vm.SyntheticModule)) {
throw new TypeError(`linker returned ${typeof m} for ${spec}; expected a vm module`);
} Type guard
const isVmModule = (v) => v instanceof vm.SourceTextModule || v instanceof vm.SyntheticModule;
Try / catch
mod.link(linker).catch((e) => {
if (e.code === 'ERR_VM_MODULE_NOT_MODULE') {
// inspect linker return values; log which specifier failed
} else throw e;
}); Prevention
- Wrap the linker so every return path is type-guarded before resolution
- Never return namespaces, strings, or null from a linker — throw your own descriptive error instead
- Keep a single copy of the node:vm module in bundles so instanceof checks stay reliable
When it happens
Trigger: Calling `module.link(async (specifier) => ...)` where the callback returns a module namespace object (`import * as ns`), a source string, a Buffer/Uint8Array of code, undefined (missing return), a plain object like {exports}, or a module created by a different loaded copy of the vm polyfill so the internal kWrap symbol does not match.
Common situations: Writing a custom linker that loads files with fs.readFileSync(path) and returns the buffer or the file content instead of wrapping it in `new vm.SourceTextModule(code, {identifier: specifier})`; forgetting the `return` keyword in an arrow-function linker; returning `null` for dependencies the developer considers optional; returning `require(...)` results instead of vm module instances.
Related errors
- ERR_VM_MODULE_DIFFERENT_CONTEXT
- ERR_MODULE_LINK_MISMATCH
- resolve hook must return { shortCircuit: true } or call next
- load hook must return { shortCircuit: true } or call nextLoa
- ERR_VM_MODULE_STATUS
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/da8fdd081e72c0ed.
Report an issue: GitHub.