Client SDK

Client SDK

The Client SDK provides everything you need to connect to a Colyseus server from your game or application.


Client Setup

The Client instance is your entry point to connect to the server.

client.ts
import { Client } from "@colyseus/sdk";
const client = new Client("http://localhost:2567");
 
// ... with full-stack type safety (optional)
import type { server } from "../../server/src/app.config.ts";
const client = new Client<typeof server>("http://localhost:2567");

Joining Rooms

Once you have a client, you can join rooms. Choose the method that best fits your matchmaking needs.

The most common way to connect. Joins an existing room if available, or creates a new one.

client.ts
try {
  const room = await client.joinOrCreate("battle", {/* options */});
  console.log("joined successfully", room);
 
} catch (e) {
  console.error("join error", e);
}

Locked or private rooms are ignored by this method.

Other Join Methods

These methods use the same pattern as joinOrCreate — replace the method name accordingly.

MethodSignatureDescription
createclient.create(roomName, options)Always creates a new room, even if others exist.
joinclient.join(roomName, options)Joins an existing room. Fails if none available. Locked/private rooms are ignored.
joinByIdclient.joinById(roomId, options)Joins a specific room by its unique ID. Private rooms can be joined by ID. Useful for invite links.
consumeSeatReservationclient.consumeSeatReservation(reservation)Joins using a pre-reserved seat from the server. See Match-maker → Reserve Seat For.

You may disallow the client from creating rooms. See Matchmaker → Restricting the frontend from creating rooms

Example: Creating an invite link with joinById

client.ts
// Share the room ID with other players
const inviteLink = `https://mygame.com/join?roomId=${room.roomId}`;
 
// On the receiving end, parse the room ID and join
const params = new URLSearchParams(window.location.search);
const room = await client.joinById(params.get("roomId"));

Send and Receive Messages

Once connected to a room, you can send and receive messages in real-time.

Sending Messages

Send messages to the room handler. Messages are encoded with MsgPack and can hold any JSON-serializable data.

client.ts
//
// sending message with string type
//
room.send("move", { direction: "left"});
 
//
// sending message with number type
//
room.send(0, { direction: "left"});

Backend: See Room → Message Handling for detailed documentation on receiving messages from the client.

Send Raw Bytes

For custom encoding, send raw byte arrays (numbers from 0 to 255).

client.js
//
// sending message with number type
//
room.sendBytes(0, [ 172, 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33 ]);
 
//
// sending message with string type
//
room.sendBytes("some-bytes", [ 172, 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33 ]);

Receiving Messages

Listen for messages sent from the server.

client.ts
room.onMessage("powerup", (message) => {
  console.log("message received from server");
  console.log(message);
});

Backend: To send a message from the server to a client you’ll need to use either client.send() or room.broadcast()


State Synchronization

The room state is automatically synchronized from the server to all connected clients. The room.state property always contains the latest state.

Use Callbacks to listen for specific property changes with fine-grained control.

client.ts
import { Callbacks } from "@colyseus/sdk";
 
const callbacks = Callbacks.get(room);
 
callbacks.listen("currentTurn", (currentValue, previousValue) => {
    console.log("Turn changed:", previousValue, "->", currentValue);
});
 
callbacks.onAdd("players", (player, sessionId) => {
    console.log("Player joined:", sessionId);
 
    callbacks.listen(player, "hp", (currentHp, previousHp) => {
        console.log("Player", sessionId, "hp:", currentHp);
    });
});
 
callbacks.onRemove("players", (player, sessionId) => {
    console.log("Player left:", sessionId);
});

See the full State Sync Callbacks documentation for all available methods including listen, onAdd, onRemove, and more.

On State Change

The onStateChange event fires whenever the server synchronizes state updates. Use this when you need to know something changed, without tracking individual properties.

room.onStateChange((state) => {
    console.log("the room state has been updated:", state);
});

Read more about State Synchronization


Connection Lifecycle

Handle the various states of a room connection.

Leaving a Room

Disconnect from the room. Use consented: true (default) for intentional leaves, or false to simulate an unexpected disconnect.

client.ts
// consented leave
room.leave();
 
// force unconsented leave
room.leave(false);

Backend: Use Room → On Leave to handle client disconnection.

Listening for the leave event:

client.ts
room.onLeave((code) => {
  console.log("client left the room");
});

Possible closing codes and their meaning:

  • 1000 - Regular socket shutdown
  • Between 1001 and 1015 - Abnormal socket shutdown
  • Between 4000 and 4999 - Custom socket close code (See more details)

Automatic Reconnection

The SDK automatically attempts to reconnect when the connection is unexpectedly dropped.

⚠️

Automatic reconnection only triggers if the room has been connected for at least minUptime milliseconds. This prevents reconnection loops for rooms that fail immediately after joining.

On Drop Event

Triggered when the connection is unexpectedly dropped. The SDK will automatically attempt to reconnect.

client.ts
room.onDrop((code, reason) => {
  console.log("connection dropped, attempting to reconnect...");
  console.log("code:", code, "reason:", reason);
});

The onDrop event is different from onLeave. While onLeave is triggered when the client intentionally leaves or the connection is permanently closed, onDrop indicates a temporary disconnection where reconnection will be attempted.

Close codes that trigger onDrop:

  • 1005 - No status received
  • 1006 - Abnormal closure
  • 1001 - Going away
  • 4010 - May try reconnect

On Reconnect Event

Triggered when the client successfully reconnects after a connection drop. While disconnected, your room.send() calls will be queued and sent to the server when the client reconnects. The maximum number of queued messages is configurable using room.reconnection.maxEnqueuedMessages.

client.ts
room.onReconnect(() => {
  console.log("successfully reconnected to the room!");
});

Reconnection Options

You may configure the reconnection behavior using room.reconnection.

Reconnection config options

OptionDefaultDescription
maxRetries15Maximum reconnection attempts
minDelay100Minimum delay between attempts (ms)
maxDelay5000Maximum delay between attempts (ms)
minUptime5000Minimum room uptime before reconnection is allowed (ms)
delay100Initial delay between attempts (ms)
maxEnqueuedMessages10Maximum buffered messages during reconnection
backoffexponentialDelay calculation function

Status properties (read-only)

PropertyTypeDescription
isReconnectingbooleanWhether currently reconnecting
retryCountnumberCurrent reconnection attempt count
enqueuedMessagesnumberBuffered messages

Customizing reconnection behavior:

client.ts
const room = await client.joinOrCreate("battle");
 
// Customize reconnection options
room.reconnection.maxRetries = 10;
room.reconnection.maxDelay = 10000; // 10 seconds max delay
room.reconnection.minUptime = 3000; // Allow reconnection after 3 seconds

Custom backoff function:

client.ts
room.reconnection.backoff = (attempt: number, delay: number) => {
  return Math.floor(Math.pow(2, attempt) * delay);
};

Manual Reconnection

For more control, you can manually reconnect using a cached reconnection token. Because this method returns a new room instance on the client, you must reattach all event listeners to the room after reconnecting.

  • You must store/cache the room.reconnectionToken from an active room connection.
  • The server needs to call .allowReconnection() for that client.
client.ts
try {
  const room = await client.reconnect(cachedReconnectionToken);
  console.log("joined successfully", room);
 
} catch (e) {
  console.error("join error", e);
}

Error Handling

Listen for errors that occur in the room.

client.ts
room.onError((code, message) => {
  console.log("oops, error occurred:");
  console.log(message);
});

Removing Listeners

Remove all event listeners from the room.


Latency & Server Selection

Measure network latency and choose optimal servers for your players.

Measuring Latency

Before Joining

Create a temporary connection to measure latency before joining a room.

Parameters

  • options.pingCount: Number of pings to send (default: 1). Returns the average latency when greater than 1.
client.ts
const latency = await client.getLatency();
console.log("Latency:", latency, "ms");
 
// With multiple pings for a more accurate average
const avgLatency = await client.getLatency({ pingCount: 5 });
console.log("Average Latency:", avgLatency, "ms");

During a Room Connection

Measure round-trip time on an existing connection.

client.ts
room.ping((latency) => {
  console.log("Latency:", latency, "ms");
});

If the connection is not open, calling ping() has no effect.

Multi-Region Server Selection

Automatically connect to the server with the lowest latency from a list of endpoints.

Parameters

  • endpoints: Array of server endpoints (URLs or endpoint settings objects).
  • options: Optional client options to pass to each client instance.
  • latencyOptions.pingCount: Number of pings to send per endpoint (default: 1).
client.ts
import { Client } from "@colyseus/sdk";
 
// Select the best server from multiple regions
const client = await Client.selectByLatency([
  "https://us-east.gameserver.com",
  "https://eu-west.gameserver.com",
  "https://asia.gameserver.com",
]);
 
// Now use the client with the lowest latency
const room = await client.joinOrCreate("game");

The method logs the latency for each endpoint to the console for debugging purposes. If all endpoints fail to respond, an error is thrown.


HTTP Requests

The client.http utility performs HTTP requests to your server endpoint. The client.auth.token is sent automatically as an Authorization header.

// GET
const response = await client.http.get("/profile");
 
// POST
const response = await client.http.post("/profile", { body: { name: "Jake" } });
 
// PUT
const response = await client.http.put("/profile", { body: { name: "Jake" } });
 
// DELETE
const response = await client.http.delete("/profile");

See Server → HTTP Routes for setting up HTTP endpoints on your server, and Authentication → HTTP Middleware for securing them.


Room Reference

Quick reference for room properties.

PropertyTypeDescription
stateanyThe synchronized room state from the server
sessionIdstringUnique identifier for the current client connection
roomIdstringUnique room ID (shareable for direct joins)
namestringName of the room handler (e.g., "battle")
reconnectionTokenstringToken for manual reconnection
reconnectionReconnectionOptionsAutomatic reconnection configuration

Next Steps