Rooms
The Room class is the core building block of Colyseus. Each room instance represents an isolated game session where clients can interact through shared state and messages.
Why Rooms?
- Isolation - Players in Room A don’t see or interact with players in Room B
- Encapsulation - Each room contains its own state, logic, and connected clients
- Scalability - Rooms are created on demand and (optionally) disposed when empty
- Flexibility - One room class can spawn many instances (e.g., multiple matches of the same game type)
Defining a Room
You can define a Room using a class that extends Room.
import { Room, Client } from "colyseus";
import { MyState } from "./MyState";
export class MyRoom extends Room {
state = new MyState();
onJoin(client: Client, options: any) {
client.send("welcome", "Welcome to the room!");
}
}Lifecycle Events
The room’s lifecycle events are called automatically. async/await is supported in all of them.
import { Room, Client, AuthContext } from "colyseus";
export class MyRoom extends Room {
// (optional) Validate client auth token before joining/creating the room
onAuth (client: Client, options: any, context: AuthContext) { }
// When room is initialized
onCreate (options: any) { }
// When the client successfully joins the room
onJoin (client: Client, options: any, auth: any) { }
// When a client disconnects unexpectedly (use allowReconnection here)
onDrop (client: Client, code?: number) { }
// When a client successfully reconnects
onReconnect (client: Client) { }
// When a client effectively leaves the room
onLeave (client: Client, code?: number) { }
// Cleanup callback, called after there are no more clients in the room. (see `autoDispose`)
onDispose () { }
}On Create
Called once, when the room is created by the matchmaker. options is the merged values specified on Server#define() with the options provided from the SDK at .joinOrCreate() or .join().
onCreate (options) {
/**
* This is a good place to initialize your room state.
*/
}The server may overwrite options during .define() for authority over client-provided options:
// (backend)
const server = defineServer({
rooms: {
my_room: defineRoom(MyRoom, { map: "cs_assault" })
}
})On this example, the map option is "cs_assault" during onCreate(), and "de_dust2" during onJoin().
On Auth
The onAuth method is called before onJoin, and it is responsible for validating the client’s request to join a room.
async onAuth (client, options, context) {
/**
* This is a good place to validate the client's auth token.
*/
}Arguments
client: A reference to the client that is trying to join the room.options: Options provided by the frontend SDK.context: The request context, containing:.token- the authentication token sent by the client..headers- the headers sent by the client..ip- the IP address of the client.
See Authentication for more details.
On Join
Triggered when a client successfully joins the room, after a successful onAuth.
Parameters:
client: A reference to the client that joined the room.options: Options provided by the Client SDK. (Merged with default values provided by Server#define())auth: (optional) auth data returned byonAuthmethod.
async onJoin (client, options, auth?) {
/**
* This is a good place to add the client to the state.
*/
}See Client SDK on .joinOrCreate() and .join().
On Drop
Triggered when a client disconnects unexpectedly (without consent). This is the recommended place to use allowReconnection().
Parameters:
client: The client that was dropped from the room.code: (optional) The close code of the leave event.
async onDrop (client, code) {
await this.allowReconnection(client, 20);
}If onDrop is not defined, onLeave will be called for both consented and non-consented disconnections.
On Reconnect
Triggered when a client successfully reconnects to the room after calling allowReconnection().
Parameters:
client: The client that reconnected to the room.
onReconnect (client) {
/**
* Client has reconnected successfully.
*/
console.log(client.sessionId, "reconnected!");
}On Leave
Triggered when a client effectively leaves the room.
- If
onDropis defined:onLeaveis called only for consented disconnections (client called.leave()). - If
onDropis not defined:onLeaveis called for both consented and non-consented disconnections.
Parameters:
client: The client that left the room.code: (optional) The close code of the leave event.
async onLeave (client, code) {
/**
* This is a good place to remove the client from the state.
*/
}You may define this function as async:
onLeave(client, code) {
if (this.state.players.has(client.sessionId)) {
this.state.players.delete(client.sessionId);
}
}At Graceful Shutdown, onLeave is called with code = CloseCode.SERVER_SHUTDOWN for all clients.
On Dispose
The onDispose() method is called before the room is destroyed, which happens when:
- there are no more clients left in the room, and
autoDisposeis set totrue(default) - you manually call
.disconnect().
async onDispose() {
/**
* This is a good place to perform cleanup tasks.
*/
}You may define async onDispose() an asynchronous method in order to persist some data in the database. In fact, this is a great place to persist player’s data in the database after a game match ends.
At Graceful Shutdown, onDispose is called for all rooms.
On Unhandled Exception
Opt-in to catch unhandled exceptions in your room. This method is called when an unhandled exception occurs in any of the lifecycle methods.
onUncaughtException (err: Error, methodName: string) {
console.error("An error occurred in", methodName, ":", err);
err.cause // original unhandled error
err.message // original error message
}See Exception Handling for more details.
On Before Patch
The onBeforePatch lifecycle hook is triggered before state synchronization, at patch rate frequency. (see patchRate)
onBeforePatch(state) {
/*
* Here you can mutate something in the state just before it is encoded &
* synchronized with all clients
*/
}On Cache Room (devMode)
An optional hook to cache external data when devMode is enabled.
(See restoring data outside the room’s state)
export class MyRoom extends Room {
// ...
onCacheRoom() {
return { foo: "bar" };
}
}On Restore Room (devMode)
An optional hook to reprocess/restore data which was returned and stored from the previous hook onCacheRoom when Development Mode is enabled.
export class MyRoom extends Room {
// ...
onRestoreRoom(cachedData: any): void {
console.log("ROOM HAS BEEN RESTORED!", cachedData);
this.state.players.forEach(player => {
player.method(cachedData["foo"]);
});
}
}On Before Shutdown
The onBeforeShutdown lifecycle hook is called as part of the Graceful Shutdown process. The process will only truly shutdown after all rooms have been disposed.
By default, the room will disconnect all clients and dispose itself immediately.
You may customize how the room should behave during the shutdown process:
onBeforeShutdown() {
//
// Notify users that process is shutting down, they may need to save their progress and join a new room
//
this.broadcast("going-down", "Server is shutting down. Please save your progress and join a new room.");
//
// Disconnect all clients after 5 minutes
//
this.clock.setTimeout(() => this.disconnect(), 5 * 60 * 1000);
}See graceful shutdown for more details.
Message Handling
Room handlers have these methods available.
On Messages
Register callbacks to process messages sent by the frontend.
messages = {
[type: string | number]: (client, payload) => void
}- The
typeargument can be eitherstringornumber. - You can only define a single callback per message type.
Callback for specific type of message
messages = {
"action": (client, payload) => {
console.log(client.sessionId, "sent 'action' message: ", payload);
}
}Use room.send(type, payload) from the client SDK to send messages to the server.
Fallback for all messages
You can register a single callback as a fallback to handle other types of messages.
messages = {
"action": (client, payload) => {
//
// Triggers when 'action' message is sent.
//
},
"*": (client, type, payload) => {
//
// Triggers when any other type of message is sent,
// excluding "action", which has its own specific handler defined above.
//
console.log(client.sessionId, "sent", type, payload);
}
}Message input validation
You may provide a validation schema using the validate() helper with a Zod schema. If validation fails, the message will be ignored.
The validated and typed data will be passed as payload on the message handler.
import { Room, validate } from "colyseus";
import { z } from "zod";
// ...
messages = {
"action": validate(z.object({
x: z.number(),
y: z.number()
}), (client, payload) => {
//
// payload.x and payload.y are guaranteed to be numbers here.
//
console.log({ x: payload.x, y: payload.y });
})
}Set State
Set the synchronizable room state. See State Synchronization and Schema for more details.
import { Room } from "colyseus";
import { MyState } from "./MyState";
export class MyRoom extends Room {
state = new MyState();
}The room’s state is mutable. You should not reassign the state object, but rather mutate it directly when updating the state.
Methods
Set Simulation Interval
Optional: Set a simulation interval that can change the state of the game. The simulation interval is your game loop. Default simulation interval: 16.6ms (60fps)
this.setSimulationInterval (callback[, milliseconds=16.6])onCreate () {
this.setSimulationInterval((deltaTime) => this.update(deltaTime));
}
update (deltaTime) {
// implement your physics or world updates here!
// this is a good place to update the room state
}Set Patch Rate
Deprecated: Use the patchRate property instead.
Set frequency the patched state should be sent to all clients. Default is 50ms (20fps)
this.setPatchRate (milliseconds)Set Private
Set the room listing as private - or revert it to public, if false is provided.
this.setPrivate (bool)Set Metadata
Set metadata to this room. Each room instance may have metadata attached to it - the only purpose for attaching metadata is to differentiate one room from another when getting the list of available rooms via Lobby Room or Match-maker API.
this.setMetadata (metadata)onCreate(options) {
this.setMetadata({ friendlyFire: options.friendlyFire });
}Set Matchmaking
Update multiple matchmaking/listing properties at once with a single persist operation. This is the recommended way to update room listing properties.
Parameters
updates: object containing the properties to updatemetadata(optional): room metadata (merged with existing metadata)private(optional): whether the room should be privatelocked(optional): whether the room should be lockedmaxClients(optional): maximum number of clients allowedunlisted(optional): whether the room should be unlisted from matchmaking queries
// Update multiple properties at once
await this.setMatchmaking({
metadata: { difficulty: "hard", rating: 1500 },
private: true,
locked: true,
maxClients: 10
});Set Seat Reservation Time
Deprecated: Use the seatReservationTimeout property instead.
Set the number of seconds a room can wait for a client to effectively join the room. You should consider how long your onAuth() will have to wait for setting a different seat reservation time. The default value is 15 seconds.
this.setSeatReservationTime (seconds)Broadcast Message
Send a message to all connected clients.
this.broadcast (type, payload, options?)Available options are:
except: aClient, or array ofClientinstances not to send the message toafterNextPatch: waits until next patch to broadcast the message
Broadcasting a message to all clients:
messages = {
"action": (client, payload) => {
// broadcast a message to all clients
this.broadcast("action-taken", "an action has been taken!");
}
}The client will receive the message in the onMessage() callback.
Lock Room
Locking the room will remove it from the pool of available rooms for new clients to connect to.
this.lock ()Unlock Room
Unlocking the room returns it to the pool of available rooms for new clients to connect to.
this.unlock ()Allow Reconnection
Allow the client to reconnect into the room.
A reconnection may be made manually (via sdk.reconnect()), or automatically in case of temporary network issues.
The .allowReconnection() method should be called on onDrop() OR onLeave() method.
this.allowReconnection (client, seconds)- The
allowReconnection()returns aDeferred<Client>instance. - The returned
Deferredinstance is a promise-like structure, you can forcibly reject the reconnection by calling.reject()on it. (see second example) Deferredtype can forcibly reject the promise by calling.reject()(see second example)
Example: Rejecting the reconnection after a 20 second timeout.
async onDrop (client: Client, code: number) {
// flag client as inactive for other users
this.state.players.get(client.sessionId).connected = false;
// allow disconnected client to reconnect into this room until 20 seconds
await this.allowReconnection(client, 20);
}
onReconnect(client: Client) {
// client returned! let's re-activate it.
this.state.players.get(client.sessionId).connected = true;
}
onLeave (client: Client, code: number) {
// Client left by consent, remove from state immediately
this.state.players.delete(client.sessionId);
}Has Reached Max Clients
Returns whether the sum of connected clients and reserved seats exceeds the maximum number of clients.
this.hasReachedMaxClients ()onCreate(options) {
if (this.hasReachedMaxClients()) {
console.log("Room is full!");
}
}Disconnect
Disconnect all clients, then dispose.
this.disconnect ()Broadcast Patch
You may not need this! - This method is called automatically by the framework.
This method will check whether mutations have occurred in the state, and broadcast them to all connected clients.
this.broadcastPatch ()If you’d like to have control over when to broadcast patches, you can do this by disabling the default patch interval:
// disable automatic patches
patchRate = null;
onCreate() {
this.clock.setInterval(() => {
// only broadcast patches if your custom conditions are met.
if (yourCondition) {
this.broadcastPatch();
}
}, 2000);
}Public properties
roomId
The unique identifier of the room. By default, a random 9-character-long string is assigned as the Room ID.
You may customize the Room ID during onCreate() by setting the this.roomId property. See Recipes » Customize Room ID
roomName
The name of the room you provided in the rooms configuration of defineServer().
state
The synchronized state of the room.
metadata
The room’s matchmaking metadata. This data is used to filter rooms during matchmaking queries.
// Get metadata
console.log(this.metadata.difficulty);
// Set metadata during onCreate() only
onCreate(options) {
this.metadata = { difficulty: "hard", rating: 1500 };
}The metadata setter can only be used during onCreate(). To update metadata after the room is created, use setMetadata() or setMatchmaking() instead.
clients
The array of connected clients. See Client instance.
Sending a message to a specific client
// ...
this.clients.forEach((client) => {
if (client.userData.team === "red") {
client.send("hello", "world");
}
});
// ...Getting a client by sessionId.
// ...
const client = this.clients.getById("UEsBFUBhK");
// ...maxClients
Maximum number of clients allowed to connect into the room. When room reaches this limit, it is locked automatically. Unless the room was explicitly locked by you via lock() method, the room will be unlocked as soon as a client disconnects from it.
patchRate
Frequency to send the room state to connected clients, in milliseconds. Default is 50ms (20fps).
class MyRoom extends Room {
patchRate = 50;
}autoDispose
Automatically dispose the room when last client disconnects. Default is true
You may disable this, and let the room stay in memory forever by
class MyRoom extends Room {
autoDispose = false;
}maxMessagesPerSecond
Maximum number of messages a client can send to the server per second. If a client sends more messages than this limit, it will be disconnected. Default is Infinity (disabled).
class MyRoom extends Room {
maxMessagesPerSecond = 30;
}seatReservationTimeout
Set the number of seconds a room can wait for a client to effectively join the room. You should consider how long your onAuth() will have to wait for setting a different seat reservation time. The default value is 15 seconds.
class MyRoom extends Room {
seatReservationTimeout = 15;
}You may set the COLYSEUS_SEAT_RESERVATION_TIME environment variable if you’d like to change the seat reservation time globally.
locked (read-only)
This property will change on these situations:
- The maximum number of allowed clients has been reached (
maxClients) - You manually locked, or unlocked the room using
lock()orunlock().
clock
It is recommended to use the clock instance for setTimeout and setInterval methods, as timers and intervals are automatically cleared when the room is disposed - preventing memory leaks.
// ...
onCreate() {
this.clock.setTimeout(() => {
console.log("This message will be printed after 5 seconds");
}, 5000);
this.clock.setInterval(() => {
console.log("Current time:", this.clock.currentTime);
}, 1000);
}
// ...See Timing Events.
presence
The presence is used as a shared in-memory database for your cluster, and for pub/sub operations between rooms.
// ...
onCreate() {
// publish an event to all rooms listening to "event-from-another-room"
this.presence.publish("event-name-from-another-room", { hello: "world" });
// subscribe to events from another room
this.presence.subscribe("event-name-from-another-room", (payload) => {
console.log("Received event from another room!", payload);
});
// set arbitrary value to the presence
this.presence.set("arbitrary-key", "value");
}
// ...See Presence API.
Client
The client instance from the backend is responsible for the transport layer between the server and the client. It should not be confused with the Client from the frontend SDK, as they have completely different purposes.
You operate on client instances from this.clients, Room#onJoin(), Room#onLeave() and Room#onMessage().
Properties
sessionId
Unique identifier of the client connection.
// ...
onJoin(client, options) {
console.log(client.sessionId);
}
// ...In the frontend, you can find the sessionId in the room instance.
userData
The client.userData can be used to store player-specific data easily accessible via the client instance. This property is meant for convenience.
// ...
onJoin(client, options) {
client.userData = { team: (this.clients.length % 2 === 0) ? "red" : "blue" };
}
onLeave(client) {
console.log(client.userData.playerNumber);
}
// ...auth
The client.auth property holds the data returned by the onAuth() method.
onAuth(token, request) {
return { userId: "123" };
}
onJoin(client, options) {
console.log(client.auth.userId);
}See Authentication for more details.
view
The client.view property is used for state filtering - allowing you to control which parts of the state a client can see.
import { StateView } from "@colyseus/schema";
onJoin(client, options) {
// Set the client's view to filter portions of the state they receive
client.view = new StateView();
}See State Synchronization → State View for more details.
reconnectionToken
The unique token used for reconnection. This token is regenerated each time the client connects.
onJoin(client, options) {
console.log(client.reconnectionToken);
}Methods
Send Message
Send a type of message to the client. Messages are encoded with MsgPack and can hold any JSON-serializable data structure.
client.send(type, payload)The type can be either a string or a number.
//
// sending message of string type ("powerup")
//
client.send("powerup", { kind: "ammo" });
//
// sending message of number type (1)
//
client.send(1, { kind: "ammo"});Send Message (in bytes)
Send a raw byte array message to the client.
client.sendBytes(type, bytes)The type can be either a string or a number.
This is useful if you’d like to manually encode a message, rather than the default encoding (MsgPack).
//
// sending message of string type ("powerup")
//
client.sendBytes("powerup", [ 172, 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33 ]);
//
// sending message of number type (1)
//
client.sendBytes(1, [ 172, 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33 ]);Leave Room
Force disconnection of the client with the room. You may send a custom code when closing the connection, with values between 4000 and 4999 (see table of WebSocket close codes)
client.leave(code?: number)This will trigger room.onLeave event on the frontend.
Table of WebSocket close codes
| Close code (uint16) | Codename | Internal | Customizable | Description |
|---|---|---|---|---|
0 - 999 | Yes | No | Unused | |
1000 | CLOSE_NORMAL | No | No | Successful operation / regular socket shutdown |
1001 | CLOSE_GOING_AWAY | No | No | Client is leaving (browser tab closing) |
1002 | CLOSE_PROTOCOL_ERROR | Yes | No | Endpoint received a malformed frame |
1003 | CLOSE_UNSUPPORTED | Yes | No | Endpoint received an unsupported frame (e.g. binary-only endpoint received text frame) |
1004 | Yes | No | Reserved | |
1005 | CLOSED_NO_STATUS | Yes | No | Expected close status, received none |
1006 | CLOSE_ABNORMAL | Yes | No | No close code frame has been received |
1007 | Unsupported payload | Yes | No | Endpoint received inconsistent message (e.g. malformed UTF-8) |
1008 | Policy violation | No | No | Generic code used for situations other than 1003 and 1009 |
1009 | CLOSE_TOO_LARGE | No | No | Endpoint won’t process large frame |
1010 | Mandatory extension | No | No | Client wanted an extension which server did not negotiate |
1011 | Server error | No | No | Internal server error while operating |
1012 | Service restart | No | No | Server/service is restarting |
1013 | Try again later | No | No | Temporary server condition forced blocking client’s request |
1014 | Bad gateway | No | No | Server acting as gateway received an invalid response |
1015 | TLS handshake fail | Yes | No | Transport Layer Security handshake failure |
1016 - 1999 | Yes | No | Reserved for future use by the WebSocket standard. | |
2000 - 2999 | Yes | Yes | Reserved for use by WebSocket extensions | |
3000 - 3999 | No | Yes | Available for use by libraries and frameworks. May not be used by applications. Available for registration at the IANA via first-come, first-serve. | |
4000 - 4999 | No | Yes | Available for applications |
Next Steps
- State Synchronization - Learn how to define and synchronize room state
- Server Configuration - Configure your Colyseus server
- Timing Events - Schedule delayed and recurring events
- Built-in Rooms - Explore Lobby and Relay rooms