jsdom/jsdom · error · TypeError
Constructor argument is not a constructor.
Error message
Constructor argument is not a constructor.
What it means
customElements.define(name, constructor) requires its second argument to be a callable constructible function/class whose [[Construct]] is usable as a custom element. jsdom checks `isConstructor(ctor)` before anything else and throws this TypeError when the argument is not a real constructor. Since jsdom wraps window objects, this can also happen when a class from a different realm/window is passed.
Source
Thrown at lib/jsdom/living/custom-elements/CustomElementRegistry-impl.js:71
}
// https://html.spec.whatwg.org/#customelementregistry
class CustomElementRegistryImpl {
constructor(globalObject) {
this._customElementDefinitions = [];
this._elementDefinitionIsRunning = false;
this._whenDefinedPromiseMap = Object.create(null);
this._globalObject = globalObject;
}
// https://html.spec.whatwg.org/#dom-customelementregistry-define
define(name, constructor, options) {
const { _globalObject } = this;
const ctor = constructor.objectReference;
if (!isConstructor(ctor)) {
throw new TypeError("Constructor argument is not a constructor.");
}
if (!isValidCustomElementName(name)) {
throw DOMException.create(_globalObject, ["Name argument is not a valid custom element name.", "SyntaxError"]);
}
const nameAlreadyRegistered = this._customElementDefinitions.some(entry => entry.name === name);
if (nameAlreadyRegistered) {
throw DOMException.create(_globalObject, [
"This name has already been registered in the registry.",
"NotSupportedError"
]);
}
const ctorAlreadyRegistered = this._customElementDefinitions.some(entry => entry.objectReference === ctor);
if (ctorAlreadyRegistered) {
throw DOMException.create(_globalObject, [
"This constructor has already been registered in the registry.",View on GitHub (pinned to 904cc9cd24)
Solutions
- Ensure the second argument is a real class: `customElements.define('x-foo', class extends HTMLElement {...})`.
- If the class lives outside the jsdom window, run the define call inside the window's script context (e.g. `window.eval` or `<script>` inside the JSDOM instance) so the class belongs to the same realm and extends the window's HTMLElement.
- Verify with `typeof C === 'function' && C.prototype && C.prototype.constructor === C` before calling define.
- Check argument order — a valid constructor in the wrong slot (e.g. define(class, 'x-foo')) will also trigger this.
Example fix
// before
customElements.define('x-foo', { connectedCallback() {} });
// after
customElements.define('x-foo', class extends window.HTMLElement { connectedCallback() {} }); Defensive patterns
Strategy: type-guard
Validate before calling
function isRealConstructor(v) {
return typeof v === 'function' && typeof v.prototype === 'object' && v.prototype !== null && /^class\s|[A-Za-z]+\s*\{/.test(Function.prototype.toString.call(v).slice(0, 200)) && (() => { try { Reflect.construct(function(){}, [], v); return true; } catch { return false; } })();
}
if (!isRealConstructor(C)) throw new TypeError('define requires a class constructor'); Type guard
const isClass = (v) => typeof v === 'function' &&
(() => { try { new Proxy(v, { construct() { return {}; } }); return true; } catch { return false; } })() &&
!Object.prototype.hasOwnProperty.call(v, 'call'); Try / catch
try {
window.customElements.define(name, C);
} catch (e) {
if (e instanceof TypeError && e.message.includes('not a constructor')) {
// fall back to defining inside the window realm via window.eval
} else { throw e; }
} Prevention
- Always extend the HTMLElement from the same window realm the registry belongs to.
- Never pass arrow functions or object literals to customElements.define.
- Keep define calls inside the jsdom window context (window.eval or inline <script>).
When it happens
Trigger: Calling customElements.define('x-foo', notAClass) — e.g. passing an object literal, an arrow function (not constructor-capable), a bound function, undefined/null, or a class defined in a different jsdom window realm via `new JSDOM().window.customElements.define(...)` with a class from the outer Node realm.
Common situations: Typo passing options object as the second argument; spreading define(name, ...args) incorrectly; passing a decorator-produced value; cross-realm class (class defined in Node vs inside the jsdom window) which fails jsdom's constructor check.
Related errors
- Invalid Sequence
- Invalid constructor prototype.
- Invalid custom element constructor return value
- Invalid constructor
- Invalid constructor, the constructor is not part of the cust
AI-assisted analysis of jsdom/jsdom@904cc9cd24 (2026-09-01).
Data as JSON: /api/errors/6e6a7260d81bb4f7.
Report an issue: GitHub.