JavaScript DOM API
JavaScript bindings — schema methods and the @read / @write / @validate / @computed resolvers declared as JavaScript modules — can traverse and build markup with a WHATWG-style DOM API. It is exposed through the ambient document global and through node/element handles.
This is a subset chosen to make isomorphic (server and browser) bindings writable; anything not listed in these pages is not available server-side. Each interface is documented on its own page:
- Document — the
documentglobal: querying, node factories, anddocumentElement/head/body. - Element — attributes, content (
innerHTML/outerHTML/textContent),classList, element traversal, and insertion. - Node — traversal and tree mutation shared by every node, plus the
Nodetype constants. - NodeList — the static collection returned by queries and child lists.
- DOMParser and XMLSerializer — parse a fresh document, serialise a node to a string.
- DOMException — the error type every thrown DOM error is an instance of, and its legacy
name → codetable.
Interfaces and instanceof
The WHATWG node interfaces exist as real classes, with working instanceof and constructor.name, in the standard hierarchy:
Node→Document,DocumentFragment,Element→HTMLElement→ per-tag interfaces (HTMLAnchorElement,HTMLInputElement, …; andHTMLMediaElement→HTMLVideoElement/HTMLAudioElement),CharacterDataCharacterData→Text,Comment
So p instanceof Element, p instanceof Node, and textNode instanceof CharacterData all hold — a binding can branch on node kind the same way it would in the browser. HTML elements (HTML-parsed or createElement) are HTMLElement (so htmlEl instanceof HTMLElement, and htmlEl.constructor.name is "HTMLElement"); elements in another namespace (createElementNS, e.g. SVG) are plain Element. Members are interface-scoped: reaching for a member that is not part of a node's interface yields undefined (for example textNode.tagName and element.data are both undefined) rather than throwing. Within a node's own interface, an accessor with no value returns null (for example element.parentNode on a detached element).
Per-tag element interfaces. Every non-deprecated HTML tag that has its own dedicated interface in the WHATWG spec has one here too, below HTMLElement — an <a> is an HTMLAnchorElement, an <input> an HTMLInputElement, and so on (a instanceof HTMLAnchorElement, a.constructor.name === "HTMLAnchorElement"; and the whole chain up to Node holds). Each has its own reference page listing its reflected IDL attributes (a.href, img.src, input.required, …) — see the Element interfaces index for the full list of interfaces and their pages. A handful of obsolete WHATWG §16.3 elements (<font>, <marquee>, …) also have their interface, marked deprecated on their page. An HTML tag with no dedicated interface — either because the spec never gave it one (<section>, <article>, …) or it isn't yet covered — is a plain HTMLElement with no reflected properties beyond HTMLElement's own (so section.href is undefined); use getAttribute / setAttribute there. A single flattened node still backs every node internally — the interfaces are a prototype layer over it.
Availability and the read-only boundary
The ambient document global is present whenever the binding runs against a document. Whether it is writable depends on the binding slot:
| Binding slot | Ambient document |
|---|---|
A default resolver (the value materialised for a new instance) |
Writable |
@read, @write, @validate, @computed; schema methods; trigger when/action |
Read-only tree, writable construction |
A document you create yourself with new DOMParser().parseFromString(…) |
Writable |
A read-only ambient document protects the existing tree: mutating a node that is already in the document — setting an attribute, changing text, inserting or removing a child — throws a NoModificationAllowedError (message: "document is read-only in this binding context"). Read-only membership is a property of the tree as it stood when the binding started, not of how you reached a node: every node that was in the document is read-only through any path (document.body, querySelector, parentNode, a NodeList, classList), so a node cannot be laundered into a writable handle by reaching it a different way.
Constructing new nodes is always allowed, even against a read-only document. createElement, createElementNS, createTextNode, createComment, createDocumentFragment, and cloneNode return fresh, writable detached nodes; building a subtree from them and returning it works with the standard DOM API:
export default (title) => {
const li = document.createElement("li");
li.textContent = title;
return li; // return an element — see "Returning DOM"
};
The one thing you cannot do is move a node out of the read-only tree into your constructed subtree — that would mutate the tree it belongs to, so card.appendChild(document.querySelector("li")) throws NoModificationAllowedError. To reuse existing content, copy it with cloneNode(true), which yields a writable copy you can attach freely:
const copy = document.querySelector("li").cloneNode(true);
card.appendChild(copy); // fine — a clone is a fresh, writable node
Mutations to a writable ambient document (a default resolver, or a document you parsed yourself) are serialised back and persisted after the binding returns.
Returning DOM from a binding
The value a binding returns is spliced into the composed document:
| Returned value | Result |
|---|---|
| An element node | Serialised and spliced in place. |
A NodeList or array of elements |
Each element serialised and spliced, in order. |
| A non-element node (text, comment, document, fragment) | An error — the message tells you the fix, e.g. return document.documentElement instead of the document, or use .textContent instead of a text node. |
Errors
Thrown errors are DOMException instances with the standard name/code/message. new DOMException(message?, name?) is available, and the full legacy name → code table is supported — see the DOMException reference. The names actually raised by this API:
| Name | Raised when |
|---|---|
SyntaxError |
Invalid CSS selector; invalid insertAdjacentHTML position. |
HierarchyRequestError |
A tree mutation would create a cycle. |
NotFoundError |
removeChild / insertBefore / replaceChild reference is not a child. |
NamespaceError |
Malformed qualified name in createElementNS / setAttributeNS. |
NoModificationAllowedError |
Mutating a node already in a read-only tree, or moving such a node into a constructed subtree (copy it with cloneNode(true) instead); outerHTML set on a parentless element. |
Divergences from the browser DOM
- Per-tag element interfaces carry reflected IDL attributes (each interface's own page lists them); a tag with no dedicated interface is a plain
HTMLElementwith no reflected properties beyondHTMLElement's own (usegetAttribute/setAttribute). Where reflection is present, two divergences are inherent to a static server DOM: value-like properties (input.value,option.value,textarea.value) reflect the content attribute (the browser'sdefaultValue), not live editing state; and URL properties (.href,.src) reflect verbatim with no base-URL resolution. See Reflected properties: shared semantics for the numeric/enum/contentEditablecanonicalization rules. querySelector/querySelectorAllcalled on an element scan the whole document, not the element's subtree (unlike the browser).getElementsByTagNameNS,closest,matches,getElementById, andchildrenare receiver/subtree-scoped.children(and all query results) are a staticNodeList, not a liveHTMLCollection.tagNamecase is keyed on the stored namespace: uppercased for elements with no namespace (HTML-parsed elements andcreateElement), verbatim forcreateElementNS.setAttributeNSstores the qualified name verbatim with no per-attribute namespace map; retrievability viagetAttributeNSdepends on an in-scopexmlns:prefix mapping the URI.createElementNSdoes not auto-emitxmlns:declarations, and itsNamespaceErrorvalidation omits the browser's reservedxml/xmlnsprefix rules.- CSS namespace-pipe selectors (
p|tag,*|tag) are unsupported (p|throwsSyntaxError;*|matches nothing). DOMParser().parseFromStringignores the MIME type (alwaystext/html).- DOM operations are charged against the request's composition budget.
See also
- JavaScript bindings — declaring
@read/@write/@validate/@computed/defaultresolvers and methods as JavaScript modules - Methods — schema methods (which can build and return DOM)
- Method Elements — invoking a method from a page; how a returned element is spliced