Your game,
your code,
with in real time.io

Multiplayer hosting for web games. Write your rooms in TypeScript, ship them with your site in one deploy, and pay nothing for a room while nobody is in it.

Request an invite$npx @irtio/cli init my-game

Invite-only while we build it.

Multiplayer hosting on demand.

Every room is a small server that runs your code. It wakes when a player connects and hibernates when the last one leaves, so your game scales with its players and costs nothing when nobody is playing.

One command deploy

irt deploy bundles your room code and your site into one build and makes it live. The site and the rooms switch together, so a client always finds the room build it was built against. The dashboard can make an earlier build live again.

How deploys work →
$ npx @irtio/cli deploy
site https://dots.irt-serve.com
rooms wss://eu.irt.io
running rooms move on their next hibernate

Rooms start when players join

A room hibernates 30 seconds after its last connection leaves, and the next connection wakes it with its state exactly as it was. Your code writes no persistence logic for this.

Lulls and spikes handled for you

There are no servers to size or keep running. Each room is placed when it wakes, so a quiet night holds nothing and a busy one gets room after room.

Simple pricing

No CCUs. You pay for the time your rooms are awake at the size you picked, not for how many players are in them.

Read how rooms hibernate and wake →

Billed while awake, free while asleep.

Room-hours run from the start of a wake until the room has hibernated, counted by IRT rather than by your code. In between, the room costs nothing for as long as it sleeps.

  1. 1

    Active

    Players are connected and the room runs its ticks. This is the time you pay for.

  2. 2

    Hibernating

    The last player left 30 seconds ago. Room state already streams out of the VM as it changes, so hibernating is a flush, not a dump.

  3. 3

    Hibernated

    The room's state sits in the state store. No process, no room-hours.

  4. 4

    Waking

    A player connects and the router holds them. Everyone held joins at once when the room is back.

Party game · join codes + voice

Party Trivia

Three to ten players on their phones. One creates a game and shares a six-letter join code, everyone talks over voice in the lobby, and late arrivals watch as spectators until the next round. No backend: guests, join codes and room creation are project settings.

How it's built →

Choose the level of control you need.

One size does not fit all. Depending on your game, you choose how much authority to leave to the clients, and a project can mix room types that make different choices.

  1. 1

    Relay

    For games that trust their clients. A native room runs no code of yours: it sends each message on to the other players and keeps presence and a leader.

  2. 2

    Shared entities

    Give a native room a schema and players spawn, own and write its entities under the rules each kind declares. Late joiners get the current state, still with no server code.

  3. 3

    Ownership

    For co-op and limited trust. Your room code hands players the entities they control, they write them with no round trip, and the room checks each write and has the last word.

  4. 4

    Fully authoritative

    For competitive games that can't allow cheating. The room runs the rules, clients predict their own movement with the room's step function, and the room rewinds to check hits where the shooter saw them.

Read about room types →
Top-down shooter · prediction + lag compensation

Arena Shooter

Two to eight players with a hitscan rifle and rockets, 30 ticks a second. Each client predicts its own player with the room's stepper, and the room rewinds to the tick the shooter saw before it checks a hit.

How it's built →

Write multiplayer like the rest of your code.

Same language, same types, one project. The client imports the schema from the same module as the room, so a site and its room build always agree.

Declare the room

Typed entities, and handlers for joins, messages and leaves. Each tick, every changed entity is encoded once as a binary delta, however many clients there are.

Room SDK →
shared.ts and room.ts
// shared.ts, imported by the room and the client
import { defineSchema, entity } from "@irtio/room";

export const Player = entity("player", {
  owner: "string", name: "string",
  x: "f32", z: "f32", hp: "u8",
});
export const schema = defineSchema({ entities: [Player] });

// room.ts
import { defineRoom, grid } from "@irtio/room";
import { Player, schema } from "./shared.js";

export default defineRoom({
  schema,
  interest: grid({ cellSize: 32, radius: 2, dims: "xz", around: (c) => c.data.me }),
  onJoin(room, conn) {
    const { name } = conn.meta as { name: string };
    conn.data.me = room.entities.create(Player, { owner: conn.player, name, hp: 100 });
  },
  onMessage(room, conn, msg) {
    const m = msg as { type?: string; text?: string };
    if (m.type === "chat") room.broadcast({ type: "chat", text: String(m.text), from: conn.player });
  },
  onLeave(room, conn) {
    room.entities.delete(conn.data.me);
  },
});

Join from the client

Connect, interpolate remote entities and send messages. A player who connects to a hibernated room sees it wake and then gets its state.

Client SDK →
client.ts
// client.ts
import { connect } from "@irtio/client";
import { Player, schema } from "./shared.js";

const room = await connect({
  router: "wss://eu.irt.io", project: "my-game", room: "forest-7", schema,
  onWaking: () => banner.show("Waking the room"),
});
room.on("restored", () => banner.hide());

const drawn = room.interpolate(Player, ["x", "z"]);
room.entities.onEnter(Player, (p) => scene.add(p.id, p.name));
room.entities.onLeave(Player, (p) => scene.remove(p.id));
scene.onFrame(() => {
  for (const p of room.entities.all(Player)) scene.move(p.id, drawn.get(p));
});

chat.onsubmit = (text: string) => room.send({ type: "chat", text });

Send each player what they can see

Grid cells for open worlds; teams, zones or dungeon levels when space doesn't split that way. Cap each connection to its closest entities, and patch voxel chunks by range.

State sync →
interest management
interest: {
  ...grid({ cellSize: 32, radius: 2, dims: "xz", around: (c) => c.data.me }),
  limit: { max: 64, priority: (c, e) => -distance(c.data.me, e) },
},

// A block edit sends a few bytes, not the chunk.
chunk.patch("blocks", offset, new Uint8Array([STONE]));

Predict the local player

The client runs the room's own step function, so the local player moves at once. When the room's answer arrives, the client replays the inputs it hasn't confirmed.

Prediction →
stepper
export const stepper = defineStepper<Input>({
  step(world, inputs, dt) {
    for (const p of world.all(Player)) {
      const input = inputs.get(p.owner);
      if (input) move(p, input, dt);
    }
  },
});

// On the client: sampled once per room tick, replayed on correction.
const me = room.predict(stepper, { find: mine, sample: readKeys });
Racing · physics + prediction + replays

Kart Race

Up to eight drivers on Rapier physics, with walls, drifting and car-on-car collisions. Every client predicts its own car with the room's stepper, standings survive the room hibernating, and races can be recorded and replayed.

How it's built →

And the hard parts, handled.

Client-side prediction

Tick clock sync, inputs tagged with the tick they are for, and unreliable messages as WebTransport datagrams with a WebSocket fallback.

Crashes and reconnection

A room that breaks is replaced from its last saved state, and its players' connections are held and moved to the new room. A dropped player reconnects to the same seat.

Proximity voice

Each listener hears the speakers it can see, closest first. A speaker's audio is permitted only to its listeners, so a modified client can't overhear anyone.

Sites

Your web client is served at <project>.irt-serve.com. Cross-origin isolation, forSharedArrayBuffer, is one flag.

Regions

Players connect to a router in the same region as their room, and router-to-room-host traffic never crosses regions.

A backend you may not need

Guests, client-created rooms, join codes and public room lists are project settings. When you do have a backend, there's an HTTP API and signed webhooks.

Numbers you can check.

Measured in our spikes. We'll publish production figures once rooms are serving players.

~35 msrestore a VM

For a VM to restore from its base snapshot and answer, in our Firecracker spike.

µswake a room

Inside a room host that's already running.

30 sto hibernate

After the last player leaves, a room hibernates on its own.

0room-hours asleep

Billed while a room hibernates, however long it sleeps.

Verifiably correct.

@irtio/testing runs rooms in process with fake clients and a controlled clock. Catch the bugs multiplayer hides before a build goes live.

With coding agents working on their own, being able to check multiplayer logic matters. A test suite lets an agent check its work before it calls it done, and the output is written for an LLM to read.

See how testing works →
room.test.ts
  • advances ticks on a controlled clock
  • hibernates and wakes the room
  • crashes the room host and restarts it
  • asserts on messages and room state

Your agent already speaks IRT.

Multiplayer is hard for agents: lots of moving parts that aren't normally testable. IRT gives your agent the docs and the tools to get it right without a human in the loop.

Try it yourself:

Read https://irt.io/llms.txt, then write a plan to add multiplayer to this game with IRT.

Docs written for agents

The docs are published as llms.txt, as one file atllms-full.txt, and every page as Markdown.

Verified without a human

Agents can't open two browsers and click, but they can run fake clients against a room and read the result.

Examples that compile

Every TypeScript example in the docs and on this page is compiled against the real packages, so the code your agent copies is the code that works.

How IRT compares.

FeatureIRTPlayroomColyseusSpacetimeDB
Server authorityYesHostYesYes
Prediction and physicsYesNoDIYDIY
VoiceYesNoNoNo
Agent-driven testingYesNoNoNo
Rooms outlive playersYesNoYesYes

No vendor lock-in.

We aim to be the best place to host your game, but you can run it wherever you like.

Your code is yours

Room code is ordinary TypeScript on Node, and the client SDKs are open source.

Host it yourself

The room host is open source and runs on its own: it accepts WebSocket connections directly and has pluggable storage.

Same code both ways

The client SDKs work against a self-hosted room host as well as against our router.

One dashboard for your live game.

  • Builds. Every build you've deployed, and the one that's live. Make an earlier one live again.
  • Rooms. The rooms running now. Read a room's state, which wakes it if it's hibernated.
  • Logs. Room host output from the last 15 minutes, then live. Also in your terminal with irt logs.
  • Usage. Room-hours against spending limits per day, week and month.
Open the dashboard →

Dead-simple pricing.

Pay for what your rooms use, from prepaid credit. No monthly fee while we're invite-only.

Room-hours

One room awake for an hour, however many players are in it. Hibernated rooms stop the meter. Priced by room size.

Traffic

The bytes your rooms send to players and receive from them.

Voice-minutes

Per participant, per minute. Only if you use voice chat.

Storage

The state your hibernated rooms keep, and your replays and clips.

Instead of CCU pricing or per-seat costs, every room is a small server and you pay for the time it's awake, not for the number of players in it. Usage is paid from prepaid credit, and every project has spending caps.

Free while you try it, by invite

We're inviting web game developers in small groups. Every invite comes with $10 of credit to start.

$10 starter credit$0 monthly fee0 room-hours asleep0 seats or CCUs
See the full pricing page →

Your game, your code, in real time.

Tell us what you're building. Email hello@irt.io with your game and your GitHub username.