MutationObserver API

By  on  

One of my favorite web tricks was using CSS and JavaScript to detect insertion and removal of a DOM node, detailed in Detect DOM Node Insertions with JavaScript and CSS Animations.  The technique and blog post were published during a time when we didn't have a reasonable API for detecting such events.  Nowadays we have MutationObserver, an API made to efficiently detect loads of node operations.  Let's have a look!

Basic MutationObserver API

The MutationObserver API is a bit complicated for me but here's the basic setup:

var observer = new MutationObserver(function(mutations) {
	// For the sake of...observation...let's output the mutation to console to see how this all works
	mutations.forEach(function(mutation) {
// Notify me of everything!
var observerConfig = {
	attributes: true, 
	childList: true, 
	characterData: true 
// Node, config
// In this case we'll listen to all changes to body and child nodes
var targetNode = document.body;
observer.observe(targetNode, observerConfig);

There's a lot to using the MutationObserver, but the breakdown is:

  • Create an instance of MutationObserver with a callback to handle any event thrown its way
  • Create a set of options for the MutationObserver
  • Call the observe method of the MutationObserver instance, passing it the node to listen to (..and its children) and the option list.
  • At the time you want to stop observing, call disconnect

MutationObserver Options

MDN provides detail on the options for MutationObserver:

  • childList: Set to true if additions and removals of the target node's child elements (including text nodes) are to be observed.
  • attributes: Set to true if mutations to target's attributes are to be observed.
  • characterData Set: to true if mutations to target's data are to be observed.
  • subtree: Set to true if mutations to not just target, but also target's descendants are to be observed.
  • attributeOldValue: Set to true if attributes is set to true and target's attribute value before the mutation needs to be recorded.
  • characterDataOldValue: Set to true if characterData is set to true and target's data before the mutation needs to be recorded.
  • attributeFilter: Set to an array of attribute local names (without namespace) if not all attribute mutations need to be observed.

That's a lot to be aware of when listening to one node and/or child nodes!

MutationRecord:  MutationObserver Handler Results

The resulting object when a mutation is observed is detailed as well:

  • type (String): Returns attributes if the mutation was an attribute mutation, characterData if it was a mutation to a CharacterData node, and childList if it was a mutation to the tree of nodes.
  • target (Node): Returns the node the mutation affected, depending on the type. For attributes, it is the element whose attribute changed. For characterData, it is the CharacterData node. For childList, it is the node whose children changed.
  • addedNodes (NodeList): Return the nodes added. Will be an empty NodeList if no nodes were added.
  • removedNodes (NodeList): Return the nodes removed. Will be an empty NodeList if no nodes were removed.
  • previousSibling (Node): Return the previous sibling of the added or removed nodes, or null.
  • nextSibling (Node): Return the next sibling of the added or removed nodes, or null.
  • attributeName (String): Returns the local name of the changed attribute, or null.
  • attributeNamespace (String): Returns the namespace of the changed attribute, or null.
  • oldValue (String): The return value depends on the type. For attributes, it is the value of the changed attribute before the change. For characterData, it is the data of the changed node before the change. For childList, it is null.

Whew.  So let's take look at some realistic use cases of MutationObserver.

Detect When a Node is Inserted

The use case in my Detect DOM Node Insertions with JavaScript and CSS Animations post was detecting node insertion, so let's create a snippet which detects node insertion:

// Let's add a sample node to see what the MutationRecord looks like
// document.body.appendChild(document.createElement('li'));

	addedNodes: NodeList[1], // The added node is in this NodeList
	attributeName: null,
	attributeNamespace: null,
	nextSibling: null,
	oldValue: null,
	previousSibling: text,
	removedNodes: NodeList[0],
	target: body.document,
	type: "childList"

The resulting MutationRecord shows addedNodes: NodeList[1], meaning that a node has been added somewhere lower in the tree.  The type is childList.

Detect When a Node is Removed

Removing a node shows the following MutationRecord:

// Now let's explore the MutationRecord when a node is removed
// document.body.removeChild(document.querySelector('div'))

	addedNodes: NodeList[0],
	attributeName: null,
	attributeNamespace: null,
	nextSibling: text,
	oldValue: null,
	previousSibling: null,
	removedNodes: NodeList[1], // The removed node is in this NodeList
	target: body.document,
	type: "childList"

This action also shows a type of childList but now removeNodes now has the NodeList[1], a NodeList with the removed node.

Detect Attribute Changes

If an attribute is changed on any element, you'll be quick to know about it; the MutationRecord will show:

// What do attribute changes look like?
// document.body.setAttribute('id', 'booooody');

	addedNodes: NodeList[0],
	attributeName: "id",
	attributeNamespace: null,
	nextSibling: null,
	oldValue: null,
	previousSibling: null,
	removedNodes: NodeList[0],
	target: body#booooody.document,
	type: "attributes"

Also note that the target will show the node for which the attributes was changed.  The oldValue will display its former value and while a normal getAttribute check give you the new attributes value.

Stop Listening!

If you're looking to write the ultimate efficient app, you'll only be adding listeners for the period you need them and then removing them when you're done:


The MutationObserver instance has a disconnect method to stop listening.  Since your app may have many, many DOM operations, you may want the power to disconnect your listener for the duration of the time your user interacts with the page.

The MutationObserver API seems a tad verbose but it's powerful, informative, and ultimately hack free.  Daniel Buchner's brilliant original "hack" provides better support for node addition and removal but MutationObserver should probably be used if possible.


Recent Features

  • By
    Send Text Messages with PHP

    Kids these days, I tell ya.  All they care about is the technology.  The video games.  The bottled water.  Oh, and the texting, always the texting.  Back in my day, all we had was...OK, I had all of these things too.  But I still don't get...

  • By
    Responsive and Infinitely Scalable JS Animations

    Back in late 2012 it was not easy to find open source projects using requestAnimationFrame() - this is the hook that allows Javascript code to synchronize with a web browser's native paint loop. Animations using this method can run at 60 fps and deliver fantastic...

Incredible Demos


  1. Do you know of a way to use MutationObserver to detect when an element’s dimension (width or height) changes? The best cross-browser solution I’ve seen so far is Cross-Browser, Event-based, Element Resize Detection, but that was published two years ago and I wonder if someone has come up with a better solution since.

  2. dharini

    I am using mutation observer on a target node. It fires if any of the child nodes or properties changes.But, I have a case where i had to handle if the target node itself is removed. This is not working.Is there a way to handle this?

  3. Guy Thomas

    I wondered what the performance difference would be between the CSS animation trick and the MutationObserver API.

    So I created a rough jsbin to test the two approaches:

    MutationObserver is consistently faster.

  4. your overview regarding MutationObserver is pretty neat, thanks for that!

    one thing though bugs me: why does the property change “checked” on an input field not get tracked?

    i’ve created a fiddle to illustrate this: enabling/disabling the input field gets tracked fine but checking/unchecking the input field does not.

    this makes no sense to me, maybe someone can explain what is going on here…thanks!

    • Stuart Simon

      You don’t use MutationObservers for that purpose. You use the onchange event.

    • the onchange event does not get triggered though when the state of the input (checked/unchecked) changes programmatically. it only works if a user clicks on the input…

  5. Nice example code, just about the only example online I could find that actually worked correctly. Thanks.

  6. Paul

    Thanks so much for this post!

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!