References

Beginner-friendly references for web development, with live, editable examples.

The JavaScript MutationObserver object

Object JavaScript All modern browsers Updated
Quick answer

A MutationObserver watches an element and runs a callback whenever its DOM changes — children added or removed, attributes changed, or text edited. new MutationObserver(callback).observe(target, options). It runs asynchronously and efficiently, making it the right tool for reacting to DOM changes you don't control.

Overview

MutationObserver notifies you when the DOM changes. You create one with a callback, then call observe(target, options) to start watching an element. Whenever a matching change happens — a child node is added or removed, an attribute is set, text content changes — your callback runs with a list of mutation records describing exactly what happened.

The options object says what to watch: childList: true for added/removed children, attributes: true for attribute changes, characterData: true for text, and subtree: true to watch all descendants, not just direct children. You only get notified about the things you opt into.

It's the modern, efficient replacement for the old approach of polling the DOM on a timer. Changes are batched and delivered asynchronously (after the current task), so it doesn't hurt performance the way constant polling would. Common uses: reacting to content injected by third-party scripts, watching for an element to appear, or keeping something in sync with DOM changes you don't directly trigger. Call disconnect() to stop observing when you're done, to avoid leaks. (For visibility specifically, use IntersectionObserver instead.)

Syntax

const observer = new MutationObserver((mutations) => {
  mutations.forEach(m => {
    // m.type, m.addedNodes, m.attributeName, ...
  });
});

observer.observe(target, {
  childList: true, attributes: true, subtree: true
});
observer.disconnect();  // stop watching

Example

Live example
<ul id="list" style="font:15px system-ui"><li>Start</li></ul>
<button onclick="add()" style="font:14px system-ui;margin-top:8px">Add item</button>
<p id="out"></p>
<script>
  const list = document.getElementById('list');
  let added = 0;

  new MutationObserver((mutations) => {
    mutations.forEach(m => { added += m.addedNodes.length; });
    document.getElementById('out').textContent = 'items added: ' + added;
  }).observe(list, { childList: true });

  let n = 0;
  function add() {
    const li = document.createElement('li');
    li.textContent = 'Item ' + (++n);
    list.appendChild(li);
  }
</script>

Best practices

  • Opt into only the changes you care about (childList, attributes, characterData, subtree).
  • Use it instead of polling the DOM on a timer — it's batched and efficient.
  • Call disconnect() when finished to avoid leaks.
  • For element visibility, use IntersectionObserver, not MutationObserver.

Frequently asked questions

What is MutationObserver used for?
Reacting to DOM changes — added/removed nodes, attribute changes, text changes — especially changes you don't directly control.
How do I watch for child elements being added?
Observe with { childList: true } (add subtree: true to watch all descendants), then read mutation.addedNodes in the callback.
Is MutationObserver synchronous?
No — changes are batched and delivered asynchronously after the current task, which keeps it efficient.
How do I stop observing?
Call observer.disconnect().