MutationObserver Options Reference

childList, attributes, subtree and characterData options with callback entry shape.

Reference for MutationObserver observe() options — childList, attributes, characterData, subtree and the OldValue and filter flags — plus every MutationRecord property and a live config validator. It runs free in your browser on Gera Tools, with nothing uploaded.

Last updated Source: Gera Tools

Why does observe() throw on my config?

MutationObserver.observe requires at least one of childList, attributes or characterData to be true. A config with only subtree or only the OldValue flags is invalid because there is nothing concrete to watch, so the call throws a TypeError.

Watching the DOM for changes

MutationObserver reports DOM changes — added or removed nodes, attribute edits, and text-content changes — asynchronously and in batches, replacing the older (and synchronous) Mutation Events API. Because it is asynchronous and batched, it is far cheaper for performance-sensitive work like live component trees, rich-text editors, or accessibility tools that need to react to external DOM manipulation. The tool’s entire behaviour is driven by the options object you pass to observe(), and the results arrive as MutationRecord entries in your callback. This reference documents every option and every record property, and validates your config against the rule that at least one of childList, attributes, or characterData must be enabled.

How it works

You create an observer with a callback, then call observe(target, options):

const mo = new MutationObserver((records) => {
  for (const r of records) {
    if (r.type === "childList") console.log(r.addedNodes, r.removedNodes);
    if (r.type === "attributes") console.log(r.attributeName, r.oldValue);
  }
});

mo.observe(node, {
  childList: true,
  attributes: true,
  attributeOldValue: true,
  subtree: true,
});

At least one of childList, attributes, or characterData must be true — if none is set, observe() throws a TypeError. subtree extends whichever flags you enabled to the entire descendant tree. The *OldValue flags and attributeFilter implicitly activate their related primary option. Callbacks run as microtasks after the current synchronous task finishes, delivering a coalesced batch — multiple mutations from one task arrive together.

All options at a glance

OptionWhat it watchesNotes
childListNodes added or removed as direct childrenRequired if watching DOM structure
attributesAny attribute changed on the targetSet attributeFilter to narrow scope
characterDataText-node content changesTarget must be a text node or use subtree
subtreeWidens any above flag to all descendantsCannot be used alone
attributeOldValueCaptures attribute’s previous valueImplies attributes: true
characterDataOldValueCaptures text’s previous valueImplies characterData: true
attributeFilterRestricts to named attributes onlyArray of strings, e.g. ["class","style"]

Common patterns and pitfalls

Watching a component tree: { childList: true, subtree: true } reports any node added or removed anywhere under the root, which is the right config for tracking framework-rendered output.

Watching a specific attribute: { attributes: true, attributeFilter: ["aria-expanded"] } fires only when aria-expanded changes, filtering out class and style churn that would otherwise flood the callback.

Feedback loops: mutating the DOM inside the callback can re-trigger the observer before it finishes. Guard with a flag or disconnect, mutate, then re-observe.

Cleanup: call mo.disconnect() when the observer is no longer needed. To drain pending records before disconnecting, call mo.takeRecords() first — records returned this way will not appear in the callback.

Invalid configs to avoid: { subtree: true } alone throws. So does any config where all three primary flags (childList, attributes, characterData) are absent or false, even if other flags are set.