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.
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.
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.
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
Active
Players are connected and the room runs its ticks. This is the time you pay for.
- 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
Hibernated
The room's state sits in the state store. No process, no room-hours.
- 4
Waking
A player connects and the router holds them. Everyone held joins at once when the room is back.
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
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
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
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
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.
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, 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
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: {
...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 →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 });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.
For a VM to restore from its base snapshot and answer, in our Firecracker spike.
Inside a room host that's already running.
After the last player leaves, a room hibernates on its own.
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 →- 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:
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.
| Feature | IRT | Playroom | Colyseus | SpacetimeDB |
|---|---|---|---|---|
| Server authority | Yes | Host | Yes | Yes |
| Prediction and physics | Yes | No | DIY | DIY |
| Voice | Yes | No | No | No |
| Agent-driven testing | Yes | No | No | No |
| Rooms outlive players | Yes | No | Yes | Yes |
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.
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.
Your game, your code, in real time.
Tell us what you're building. Email hello@irt.io with your game and your GitHub username.