Room types
A project can define several kinds of room. Each room type is its own file with its own schema, tick rate, memory budget and retention window, and you address a type by putting its name in front of the room id.
Use room types for distinct parts of a game, such as a match and a notifications hub.
The directory
Put each room in irtio/rooms/, one file per type, each with its own default export:
irtio/
rooms/
default.ts the default type
arena.ts the arena type
notifications.ts The filename is the type name. Type names are lowercase, start with a letter or a digit, use only a-z 0-9 - _, and run to 24 characters.
irtio/room.ts defines the default room type. If you have both, irtio/room.ts is the default type and irtio/rooms/ holds the others.
Addressing
The room type goes in the room id, in front of a colon:
arena:x7Qb a room of the arena type
notifications:global the notifications hub
lobby the default type
AB23 the default type, from a shared link A room id without a prefix uses the default type.
On the client, pass the whole room id, with the schema that type exports:
// main.ts
import { joinRoom } from '@irtio/client';
import { arenaSchema } from './irtio/rooms/arena-schema';
const room = await joinRoom(arenaSchema, { room: 'arena:x7Qb' }); Two rules:
- The colon is the only separator, and it is never a slash. A room id is one opaque string
everywhere below the join: snapshot keys, player storage, alarms,
irtio logs --room. Nothing downstream splits it. - A plain id and
default:are different rooms.lobbyanddefault:lobbyboth run the default type’s code, and they are two rooms with two sets of state, because the room id is the key. Pick one spelling and stay with it.
Each type brings its own schema
Every type has its own defineSchema, so the arena’s collections and the hub’s are unrelated. The
convention is a schema module per type beside the room file:
irtio/
rooms/
arena.ts
arena-schema.ts
notifications.ts
notifications-schema.ts A join is checked against the schema of the type it names. Joining arena:x7Qb with the
notifications schema is refused with a schema mismatch, even though both schemas belong to the same
project, and the error says which type it was checking.
There is no code generator. The schema a client joins with is the module it imports.
Each type declares its own size class
A room type says which size class its rooms run in:
// irtio/rooms/notifications.ts
export default defineRoom(notificationsSchema, {
mode: 'event',
class: 'standard', // 'standard' | 'large' | 'extra-large'
// ... handlers
}); The class sets the room worker’s heap ceiling: 64 MB Standard, 256 MB Large, 1 GB Extra large. It is a real limit. A room that goes past it dies as a room crash, and its siblings keep serving. The class also decides what the room is billed at, on the pricing table.
Omit class and the type is Standard.
How many rooms fit on one server is irtio’s number, not yours: 20 Standard, 8 Large or 3 Extra large. See room budget. Your project’s server is sized for the largest class it declares, and a project that fills one server gets another.
Each type decides how long its rooms are kept
A room’s state lives in storage after it goes to sleep, and by default it lives there forever. That is the right default for a save file and the wrong one for a match nobody will open again. A type can say how long its rooms outlive their last activity:
// irtio/rooms/match.ts
export default defineRoom(matchSchema, {
retention: '10m', // delete a room of this type 10 minutes after it last slept
// ... handlers
}); Ten minutes after a room of this type last went to sleep, irtio deletes its stored state: its snapshot, every save generation it wrote, and its durable alarms. Player storage and leaderboard scores are keyed to the player rather than to the room, so they are not touched, and neither is anything belonging to any other room.
The value is a duration string: one whole number and one unit, m, h or d. '10m', '6h', '30d'. The shortest window is one minute and the longest is 3650 days. Anything else is refused
when you build, by name, with the value you wrote quoted back at you. There is no '1h30m' and no
seconds.
The window is a floor, not a deadline. irtio sweeps for expired rooms every 15 minutes, so retention: '10m' means at least ten minutes, then deleted on the next sweep. Do not build anything
that depends on the exact moment state disappears.
Leaving the field out means forever. Removing the field from a type, or rolling back to a version that did not have it, stops the deletions: irtio only ever acts on what your current deployment says.
Two things are never deleted, whatever the window says. A room that is awake, or that has a restore queued, or that has a durable alarm armed for the future, is left alone until that stops being true. And there is one race: if somebody joins a room in the few seconds between irtio checking that it is asleep and deleting it, that join wins and the room runs from its snapshot, but its save generations are already gone.
The rps example game declares retention: '10m' on its match rooms. A best-of-three that ended
twenty minutes ago is state nobody will ask for.
Keeping one room longer than its type says
The declaration is about a type. Sometimes you need one room: a match a player has queried, a room you are debugging, a room a support ticket is about. That is an override, set on the room itself:
npx irtio rooms set arena-7 --retention 30d # keep this one for a month
npx irtio rooms set arena-7 --retention forever # keep it indefinitely
npx irtio rooms set arena-7 --retention clear # back to what its type declares An override wins over the declaration in both directions, so a short one is also how you dispose of
a single room on a schedule without touching the type. irtio rooms marks an overridden window
with a *, and irtio rooms get names it.
You can also see when a room will go, which the declaration alone does not tell you:
ROOM STATUS RETAIN DELETES LAST SEEN
arena-7 hibernated 30d* in 29d 2026-08-30T11:02:41Z The column on the listing is approximate (it is measured from the last report rather than from the
stored snapshot’s own timestamp); irtio rooms get gives the exact instant. Either way it is still
the floor the sweep acts after, not a promise about the moment.
The same two things are on the rooms API, which is what a nightly job would use.
Deploys move every type together
A deploy is one version of one project. All the types in it ship together, and a rollback takes all of them back together. There is no way to deploy or roll back a single type. Types share a project’s stored state, and a version where the arena had rolled back and the hub had not is a project whose data disagrees with itself.
The practical consequences:
- A deploy that changes three schemas is one version with three schema changes in it, classified as a whole. If any of them is breaking, the deploy is breaking.
- A room that slept across a deploy wakes on the newest version and migrates against its own type’s schema chain.
- A rollback returns every type to the target version, and every room to the state it had there.
Related
- Migrations for what a schema change costs and how to write one.
- Hibernation for what sleeping and waking do to a room.