All guides

How to add multiplayer to a browser game

Add one script tag, call join with your key and a room name, set fields on your own player, and read everyone else's from the state callback. There is no server to write and no build step: an existing single-player HTML game usually needs under twenty lines.

01The whole thing

This is the complete integration. Everything after it is refinement.

<script src="https://…/mp.global.js"></script>
<script>
  const mp = await MP.join({
    key: "pk_live_your_key",       // from your dashboard
    room: "my-game",               // any name; it is created on first join
    endpoint: "wss://…",           // realtime server
    apiUrl: "https://…",           // control plane
  });

  mp.me.set({ x: 100, y: 50 });    // your own player, synced to everyone
  mp.setShared("turn", "x");       // room-wide state

  mp.onState((state) => {
    const players = state.entities.players; // every player, live
    draw(players);
  });

  window.addEventListener("pagehide", () => mp.leave());
</script>

Copy your real key and the two URLs from the docs; they are per-account.

02How the state model works

There are two places to put data. Your own player's fields, which only you can write, and shared room state that anyone can write.

That split is what makes the default safe: a client literally cannot address another player's entity, so the common cheat of writing someone else's position is impossible by construction rather than by validation.

your browserme.set("x", 100)write: allowedothers[id].x = 0not addressablethe roomyouthemshared.turnanyone may writeevery clientonState()reads everything
You may write your own player and nothing else. There is no message that addresses another player's entity, so the most common cheat is impossible by construction rather than blocked by validation.

03Wiring it into an existing game loop

Most single-player games already have a loop that updates a local player and draws the world. The change is small: keep updating your local player as you do now, push its position with a set call, and draw everyone from the state callback instead of only your own object.

Do not send every frame. Setting a field on input or on a fixed tick: ten to twenty times a second, looks identical to a player and uses a fraction of the traffic.

inputkeys, tapsupdatemove your playerme.set10 to 20 times a secondonStateeveryone else arrivesdrawrender them allevery frame
Where the network sits in a loop you already have. Input and movement stay local; you publish your position on a fixed tick and draw everyone from the state callback instead of only yourself.

04The two things everyone gets wrong

  • Not leaving on unload. A refresh that did not leave cleanly holds the seat for about thirty seconds, so the player appears twice to everyone else. The pagehide listener in the snippet above is the fix.
  • Forgetting the control-plane URL. Omit it and the room still works, which is what makes it confusing, but analytics, identity and leaderboards silently do nothing.

05Then make it fair

Everything above is a relay: clients say where they are and are believed. That is correct for a co-op or sandbox game and wrong the moment a score matters. When it does, move the rules into server logic that runs authoritatively, so the client sends inputs rather than outcomes.

Common questions

Do I need a server?

No. Rooms are created on first join and the server already exists. You only deploy code if you want rules enforced server-side, and that is a small script rather than a service to run.

How many players fit in one room?

Thirty-two by default, raisable to 256 in the room settings, and always bounded by your plan's per-game concurrent-player limit.

Does it work in a single HTML file?

Yes. The script tag exposes a global, so a file you open in a browser works with no build step and no bundler.

Try it on your own game

The free tier does not expire and asks for no card. Add one script tag and have two people playing in about a minute.