Use this file to discover all available pages before exploring further.
PartyServer maps cleanly onto the classic multiplayer model: each room becomes a single Durable Object instance with one authoritative copy of the game state. Every player connects to the same DO, the server applies updates and broadcasts deltas, and the WebSocket hibernation API keeps costs near zero even when thousands of rooms sit idle between moves.
A Durable Object is a single-threaded, location-aware, stateful compute unit. That means there is exactly one instance of your game room running at a time — no split-brain, no synchronization between replicas. PartyServer wraps that primitive with lifecycle hooks (onConnect, onMessage, onClose) and a broadcast helper so you can focus on game logic rather than infrastructure.
Single authoritative state
One DO instance per room = one source of truth. No distributed locks or conflict resolution needed.
Connection-scoped identity
Attach player metadata directly to the connection object with connection.setState().
Cheap at rest
Hibernation mode lets the DO sleep between messages, billing only for active compute time.
Location awareness
Pass a locationHint to routePartykitRequest to place a room close to its players.
Keep the authoritative state as an in-memory field on the server class. Because there is only one instance per room, reads are instant and writes never conflict.
// server.tsimport { Server, routePartykitRequest } from "partyserver";import type { Connection } from "partyserver";import { env } from "cloudflare:workers";type Player = { id: string; x: number; y: number };export class GameRoom extends Server { static options = { hibernate: true }; // In-memory world state — one copy per room players = new Map<string, Player>(); onConnect(connection: Connection) { // Send the current world state to the new player const snapshot = JSON.stringify({ type: "snapshot", players: [...this.players.values()], }); connection.send(snapshot); } onMessage(connection: Connection, message: string) { const data = JSON.parse(message); if (data.type === "move") { // Update this player's position this.players.set(connection.id, { id: connection.id, x: data.x, y: data.y, }); // Broadcast the delta to every other player this.broadcast( JSON.stringify({ type: "move", id: connection.id, x: data.x, y: data.y }), [connection.id] // exclude the sender ); } } onClose(connection: Connection) { // Clean up and notify remaining players this.players.delete(connection.id); this.broadcast( JSON.stringify({ type: "leave", id: connection.id }), [connection.id] ); }}export default { async fetch(request: Request): Promise<Response> { return ( (await routePartykitRequest(request, env)) || new Response("Not Found", { status: 404 }) ); },} satisfies ExportedHandler<Env>;
connection.setState() attaches up to 2 KB of arbitrary data to a connection. This data survives hibernation and is available in every subsequent lifecycle hook — ideal for player names, authentication tokens, or team assignments.
import type { Connection, ConnectionContext } from "partyserver";type PlayerState = { name: string; team: "red" | "blue" };onConnect(connection: Connection<PlayerState>, ctx: ConnectionContext) { const url = new URL(ctx.request.url); const name = url.searchParams.get("name") ?? "Anonymous"; const team = (url.searchParams.get("team") as PlayerState["team"]) ?? "red"; // Persist player identity on the connection itself connection.setState({ name, team }); this.broadcast( JSON.stringify({ type: "join", id: connection.id, name, team }), [connection.id] );}
The PartyKit repository ships two fixtures that demonstrate these patterns at production scale:
tldraw fixture
A collaborative whiteboard using tldraw’s sync store backed by a PartyServer DO.
Globe fixture
A live globe that tracks every visitor’s geographic position using connection.setState() and Cloudflare geolocation headers.
The globe fixture in particular is a clean reference for the connect → state → broadcast → disconnect lifecycle: on connect it reads request.cf.latitude/longitude, persists that to conn.setState(), broadcasts a marker to all peers, and removes the marker in onClose.