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:
- Position coordinates (
x,y) - Current angle and rotation
- Linear and angular velocity
- Dimensions or geometry type
- Static or dynamic state flags
- Custom labels or rendering properties
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):
- Custom Vertices: Instead of tracking
widthandheight, store the array of vertex offsets usingbody.vertices.map(v => ({ x: v.x, y: v.y }))and recreate the body usingMatter.Bodies.fromVertices(). - Constraints: Create a separate array during
serialization for
Matter.Composite.allConstraints(engine.world). Store the constraint'slength,stiffness, and the unique IDs or array indices of the connectedbodyAandbodyB. When restoring, re-link the constraints usingMatter.Constraint.create()after all bodies have been instantiated.