Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/cloudflare/partykit/llms.txt

Use this file to discover all available pages before exploring further.

Every WebSocket client that connects to a Server is represented as a Connection — a standard WebSocket augmented with an id, tags, and a small state slot. PartyServer calls lifecycle hooks on your server class at each stage of a connection’s life, giving you clean entry points for initialization, message handling, and cleanup. This page walks through those hooks and the APIs for working with the set of live connections.

Connection lifecycle

1

onStart

Called once when the Durable Object first boots (or wakes from hibernation). Use this to load persistent data from storage. It runs inside ctx.blockConcurrencyWhile, so no messages are processed until it resolves.
2

onConnect

Called when a new client completes the WebSocket handshake. Receives the Connection and a ConnectionContext containing the originating Request. This is the right place to initialize per-connection state.
3

onMessage

Called each time the client sends a message. Receives the Connection and the raw WSMessage (string | ArrayBuffer | ArrayBufferView).
4

onClose

Called when the client closes the connection. Receives the connection, WebSocket close code, reason string, and a wasClean boolean.
5

onError

Called when a transport error occurs. Receives the connection and the error value. Does not fire for normal close events.
All hooks are optionally async.
import { Server } from "partyserver";
import type { Connection, ConnectionContext, WSMessage } from "partyserver";

export class ChatServer extends Server {
  onStart() {
    console.log("Server started:", this.name);
  }

  onConnect(connection: Connection, ctx: ConnectionContext) {
    console.log("Connected:", connection.id, "from", ctx.request.url);
    connection.send(JSON.stringify({ type: "welcome", id: connection.id }));
  }

  onMessage(connection: Connection, message: WSMessage) {
    console.log("Message from", connection.id, ":", message);
    this.broadcast(message, [connection.id]);
  }

  onClose(connection: Connection, code: number, reason: string) {
    console.log("Closed:", connection.id, code, reason);
  }

  onError(connection: Connection, error: unknown) {
    console.error("Error on", connection.id, ":", error);
  }
}

The Connection object

A Connection extends the standard WebSocket interface with these additional properties:
PropertyTypeDescription
idstringUnique ID for the connection (nanoid, or _pk query param).
tagsstring[]Array of tags assigned by getConnectionTags.
serverstringThe name of the server this connection belongs to.
stateConnectionState<T>Arbitrary state (up to 2 KB).
setState(value)functionUpdate the connection’s state.

Connection state

Each connection can hold up to 2 KB of arbitrary data in connection.state. This is stored in the Hibernation API’s per-socket attachment slot, so it survives hibernation and is available when a message arrives on a previously sleeping socket. Use connection.setState() in onConnect to attach user-specific data once the connection is established:
type UserState = {
  userId: string;
  role: "admin" | "member";
};

onConnect(connection: Connection<UserState>, ctx: ConnectionContext) {
  const url = new URL(ctx.request.url);
  const userId = ctx.request.headers.get("x-user-id") ?? "anonymous";
  const role = url.searchParams.get("role") === "admin" ? "admin" : "member";

  connection.setState({ userId, role });
}
You can then read connection.state anywhere you have access to a Connection:
onMessage(connection: Connection<UserState>, message: WSMessage) {
  const { userId, role } = connection.state;
  console.log(`Message from ${role} ${userId}: ${message}`);
}
setState also accepts an updater function:
connection.setState((prev) => ({ ...prev, messageCount: (prev?.messageCount ?? 0) + 1 }));

Tagging connections

Override getConnectionTags to attach one or more string labels to a connection at connect time. Tags are stored alongside the connection and can be used to filter the getConnections iterator.
getConnectionTags(connection: Connection, ctx: ConnectionContext): string[] {
  const url = new URL(ctx.request.url);
  const role = url.searchParams.get("role") ?? "member";
  // Each connection supports up to 9 tags, max 256 chars each
  return [role];
}

Iterating and looking up connections

getConnections(tag?)

Returns an iterable of all live connections, optionally filtered to those that carry the given tag. Pass a single tag string.

getConnection(id)

Returns the single Connection with the given ID, or undefined if it is not currently open.
// Iterate all connections
for (const conn of this.getConnections()) {
  conn.send("ping");
}

// Iterate only admin connections
for (const conn of this.getConnections<UserState>("admin")) {
  conn.send(JSON.stringify({ type: "admin-notice", text: "Heads up!" }));
}

// Look up a specific connection
const conn = this.getConnection<UserState>("abc123");
if (conn) {
  conn.send("direct message");
}

Broadcasting

broadcast(message, exclude?) sends a message to every live connection. Pass an array of connection IDs in the second argument to skip those connections:
onMessage(connection: Connection, message: WSMessage) {
  // Send to everyone except the sender
  this.broadcast(message, [connection.id]);
}

Full example: role-based broadcast

This example tags connections by role at connect time, stores state, and sends admin-only notices:
import { Server } from "partyserver";
import type { Connection, ConnectionContext, WSMessage } from "partyserver";

type UserState = { userId: string; role: "admin" | "member" };

export class RoleAwareServer extends Server {
  // Tag each connection with the user's role
  getConnectionTags(
    connection: Connection,
    ctx: ConnectionContext
  ): string[] {
    const role =
      new URL(ctx.request.url).searchParams.get("role") ?? "member";
    return [role === "admin" ? "admin" : "member"];
  }

  onConnect(connection: Connection<UserState>, ctx: ConnectionContext) {
    const url = new URL(ctx.request.url);
    connection.setState({
      userId: url.searchParams.get("userId") ?? "anon",
      role: (url.searchParams.get("role") as UserState["role"]) ?? "member"
    });
    // Greet only the joining connection
    connection.send(
      JSON.stringify({ type: "welcome", role: connection.state.role })
    );
  }

  onMessage(connection: Connection<UserState>, message: WSMessage) {
    const { role } = connection.state;
    const parsed = JSON.parse(message as string) as {
      type: string;
      text?: string;
    };

    if (parsed.type === "admin-broadcast" && role === "admin") {
      // Only admins can trigger a broadcast to all admins
      for (const admin of this.getConnections<UserState>("admin")) {
        admin.send(
          JSON.stringify({ type: "admin-notice", text: parsed.text })
        );
      }
    } else {
      // Regular chat: echo to everyone else
      this.broadcast(message, [connection.id]);
    }
  }
}
Tags are set once in getConnectionTags and cannot change during a connection’s lifetime. If you need mutable metadata, use connection.setState() instead.

Build docs developers (and LLMs) love