How to Save Matter.js World Snapshots to LocalStorage

This article explains how to capture and persist the state of a Matter.js physics simulation to browser localStorage using JSON. Because Matter.js physics worlds contain complex circular references and methods that cannot be serialized directly with JSON.stringify(), you must extract essential body properties into a plain JavaScript object to create a valid snapshot. Below, you will learn why direct serialization fails, what properties to extract, and how to reconstruct the physics world upon retrieval.

The Serialization Challenge in Matter.js

Running JSON.stringify(engine.world) throws a TypeError: Converting circular structure to JSON. A Matter.js World or Composite contains references pointing back and forth between constraints, parts, parents, and engines.

To bypass this limitation, you must extract only the raw physical state required to rebuild each body:

Extracting and Saving the World Snapshot

To save the simulation, traverse all bodies using Matter.Composite.allBodies(), map each body to a lightweight data transfer object, stringify the resulting array, and write it to localStorage.

function saveWorldSnapshot(engine, storageKey = 'matter_snapshot') {
  // Retrieve all bodies currently inside the simulation world
  const bodies = Matter.Composite.allBodies(engine.world);

  const serializedBodies = bodies.map(body => {
    return {
      label: body.label,
      isStatic: body.isStatic,
      position: { x: body.position.x, y: body.position.y },
      angle: body.angle,
      velocity: { x: body.velocity.x, y: body.velocity.y },
      angularVelocity: body.angularVelocity,
      // Record shape parameters to reconstruct the body geometry
      isCircle: Boolean(body.circleRadius),
      circleRadius: body.circleRadius || null,
      width: body.bounds.max.x - body.bounds.min.x,
      height: body.bounds.max.y - body.bounds.min.y,
      friction: body.friction,
      restitution: body.restitution
    };
  });

  // Convert the array to a JSON string and store it
  const snapshotJSON = JSON.stringify(serializedBodies);
  localStorage.setItem(storageKey, snapshotJSON);
}

Restoring the World from LocalStorage

Restoring the snapshot requires retrieving the string, parsing it with JSON.parse(), clearing the existing world bodies, and rebuilding each body with the proper dimensions and state.

function restoreWorldSnapshot(engine, storageKey = 'matter_snapshot') {
  const snapshotData = localStorage.getItem(storageKey);
  if (!snapshotData) {
    console.warn('No snapshot found in localStorage.');
    return;
  }

  const parsedBodies = JSON.parse(snapshotData);

  // Clear existing bodies while retaining constraints or engine bindings if needed
  Matter.Composite.clear(engine.world, false);

  const restoredBodies = parsedBodies.map(data => {
    let body;

    // Recreate the shape using factory methods
    if (data.isCircle) {
      body = Matter.Bodies.circle(data.position.x, data.position.y, data.circleRadius, {
        isStatic: data.isStatic,
        friction: data.friction,
        restitution: data.restitution,
        label: data.label
      });
    } else {
      body = Matter.Bodies.rectangle(data.position.x, data.position.y, data.width, data.height, {
        isStatic: data.isStatic,
        friction: data.friction,
        restitution: data.restitution,
        label: data.label
      });
    }

    // Restore dynamic transformation and motion vectors
    Matter.Body.setAngle(body, data.angle);
    Matter.Body.setVelocity(body, data.velocity);
    Matter.Body.setAngularVelocity(body, data.angularVelocity);

    return body;
  });

  // Re-insert the rebuilt bodies into the physics world
  Matter.Composite.add(engine.world, restoredBodies);
}

Handling Complex Vertices and Constraints

If your simulation utilizes irregular polygons or distance constraints (springs, joints):

  1. Custom Vertices: Instead of tracking width and height, store the array of vertex offsets using body.vertices.map(v => ({ x: v.x, y: v.y })) and recreate the body using Matter.Bodies.fromVertices().
  2. Constraints: Create a separate array during serialization for Matter.Composite.allConstraints(engine.world). Store the constraint's length, stiffness, and the unique IDs or array indices of the connected bodyA and bodyB. When restoring, re-link the constraints using Matter.Constraint.create() after all bodies have been instantiated.