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.

A single Durable Object instance is billed by active CPU time. When no WebSocket messages are flowing, a non-hibernating DO stays fully loaded in memory — accumulating charges and occupying a Worker slot. Hibernation solves this by evicting the DO from memory when its connections go idle, waking it on-demand when a message arrives. For high-throughput channels that exceed what one DO can handle, partysub distributes clients across multiple shards and fans messages between them.

Hibernation

Enable hibernation by setting a static options property on your server class:
import { Server } from "partyserver";

export class ChatServer extends Server {
  static options = {
    hibernate: true
  };

  onMessage(connection, message) {
    this.broadcast(message, [connection.id]);
  }
}
With hibernate: true, Cloudflare’s runtime parks the DO in cold storage when no messages are active. The next incoming message re-creates the isolate and resumes processing. All PartyServer lifecycle hooks — onStart, onConnect, onMessage, onClose, and onAlarm — are called exactly as they would be in non-hibernating mode, so no application code needs to change.

What happens on wake

When a hibernating DO receives a message, the runtime:
  1. Restores the DO state from durable storage.
  2. Calls onStart() (inside ctx.blockConcurrencyWhile) so you can reload any in-memory data.
  3. Dispatches the waiting message to onMessage (or onConnect / onClose as appropriate).
This means onStart may run more than once per DO lifetime — once on cold boot, and again after each hibernation cycle. Keep onStart idempotent and load only what the DO needs to handle a single request.
export class HibernatingCounter extends Server {
  static options = { hibernate: true };

  count = 0;

  // Called on every wake — load fresh from storage
  async onStart() {
    this.count = (await this.ctx.storage.get<number>("count")) ?? 0;
  }

  async onMessage(connection, message) {
    const { type } = JSON.parse(message as string) as { type: string };
    if (type === "increment") {
      this.count += 1;
      await this.ctx.storage.put("count", this.count);
      this.broadcast(JSON.stringify({ count: this.count }));
    }
  }
}

Inherited hibernation

Subclasses automatically inherit the nearest explicitly configured hibernate value from their prototype chain. You do not need to re-declare static options on every subclass:
// Base class enables hibernation
export class BaseServer extends Server {
  static options = { hibernate: true };
}

// Inherits hibernate: true from BaseServer
export class RoomServer extends BaseServer {
  onMessage(connection, message) {
    this.broadcast(message);
  }
}

// Overrides to disable hibernation for this subclass only
export class LiveUpdateServer extends BaseServer {
  static options = { hibernate: false };
}
Declaring partial static options on a child class (e.g. adding a different key) does not disable hibernation inherited from a parent. The resolution walks up the prototype chain and uses the first explicitly set hibernate value it finds.

Fan-out at scale with partysub

A single Durable Object handles one room at a time and is subject to Cloudflare’s per-isolate connection limits. For high-traffic channels, partysub distributes clients across multiple DO shards and synchronizes publishes between them:
import { createPubSubServer } from "partysub/server";

const { PubSubServer, routePubSubRequest } = createPubSubServer({
  binding: "PubSub",
  nodes: 100,          // 100 shards per channel
  locations: {
    wnam: 3,           // Weight North America more heavily
    weur: 1
  }
});

export { PubSubServer };

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    return (
      (await routePubSubRequest(request, env)) ||
      new Response("Not found", { status: 404 })
    );
  }
};
routePubSubRequest reads the client’s cf.country from the request and routes them to a shard in the nearest configured region. When a shard receives a publish, it fans the message out to all other shards in that channel so every subscriber receives it regardless of which shard they landed on.
partysub is experimental and not yet recommended for production. The API may change without a major version bump.

Geographic routing with locationHint

locationHint is a best-effort suggestion that asks Cloudflare to create or access a DO in a specific region. This reduces latency when you know where most of your users are located:
await routePartykitRequest(request, env, {
  locationHint: "weur" // Western Europe
});
Valid values include "wnam", "enam", "sam", "weur", "eeur", "apac", "oc", "afr", and "me". Location hints are advisory — Cloudflare may place the DO elsewhere if the region is unavailable.

Data residency with jurisdiction

For GDPR compliance or other regulatory requirements, jurisdiction is a hard constraint that prevents a DO from ever leaving the specified legal territory:
await routePartykitRequest(request, env, {
  jurisdiction: "eu"
});
You cannot combine jurisdiction with location-weighted partysub options. If both are specified, createPubSubServer will throw at startup.
Both locationHint and jurisdiction are also available on getServerByName:
const stub = await getServerByName(env.MyServer, "room-42", {
  locationHint: "apac",
  jurisdiction: "eu"
});

How Durable Objects handle global distribution

Durable Objects are globally unique by name — there is exactly one active instance of my-server/room-42 in the world at any given time, regardless of how many edge nodes your Worker runs on. Cloudflare routes all traffic for that name to the single authoritative isolate. This uniqueness guarantee is what makes PartyServer’s strong consistency model work: every connection to the same room lands in the same process, so broadcasts, shared state, and storage writes are trivially serialized without any cross-node coordination. The trade-off is that a single very popular room is constrained to the throughput of one isolate — which is where hibernation (to reduce cost at low traffic) and partysub sharding (to multiply capacity at high traffic) complement each other.

Build docs developers (and LLMs) love