Modifying Matter.js Bodies on collisionStart

Yes, you can modify body properties during a collisionStart event in Matter.js, but the method you use depends heavily on which properties you change. While superficial properties, forces, and velocities can often be adjusted immediately, structural alterations—such as removing a body, adding constraints, or toggling static states—can disrupt the internal collision solver and cause simulation errors. This guide covers what you can safely alter immediately and how to defer critical changes until the physics step completes.

Safe Direct Modifications

During the collisionStart event, Matter.js has detected overlapping pairs and is preparing to resolve (or has just begun resolving) the collision response. You can directly update non-structural properties without breaking the engine pipeline:

Matter.Events.on(engine, 'collisionStart', (event) => {
    event.pairs.forEach((pair) => {
        const { bodyA, bodyB } = pair;

        // Directly modify velocity or custom flags
        if (bodyA.label === 'player') {
            Matter.Body.setVelocity(bodyA, { x: 0, y: -10 });
            bodyA.render.fillStyle = '#ff0000';
        }
    });
});

Problematic Modifications During collisionStart

Attempting to change structural or collision-mesh definitions during the collision callback can crash the engine or lead to "ghost" collisions. Avoid the following actions directly inside collisionStart:

If you attempt to delete a body while Matter.js is still iterating through collision pairs involving that body, the solver may attempt to read undefined properties on subsequent iterations.

The Solution: Deferred Execution

To safely perform structural modifications triggered by a collision, defer them until the current engine step concludes. You can achieve this cleanly by listening to the afterUpdate event or using a modification queue.

// A queue to hold changes needed after the physics step
const deferredTasks = [];

Matter.Events.on(engine, 'collisionStart', (event) => {
    event.pairs.forEach((pair) => {
        if (pair.bodyA.label === 'bullet') {
            // Queue destructive or structural changes
            deferredTasks.push(() => {
                Matter.Composite.remove(engine.world, pair.bodyA);
                Matter.Body.setStatic(pair.bodyB, true);
            });
        }
    });
});

// Execute the queue once the solver finishes calculating the current step
Matter.Events.on(engine, 'afterUpdate', () => {
    while (deferredTasks.length > 0) {
        const task = deferredTasks.shift();
        task();
    }
});

Using this pattern prevents race conditions, preserves the integrity of the broadphase collision tree, and ensures your simulation remains stable while reacting dynamically to collisions.