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.

partysub brings publish-subscribe messaging to Cloudflare Workers. Instead of routing all clients for a channel through a single Durable Object — which limits concurrency — partysub shards each channel across multiple DO nodes and uses location hints to connect users to a nearby shard. Clients subscribe to topics or wildcard patterns; publishers POST or send JSON payloads that fan out to all matching subscribers.
partysub is experimental and is not yet recommended for production use. APIs may change between releases.

Architecture

Channel "news"
├── Shard 0  (wnam) ── clients in North America
├── Shard 1  (wnam) ── clients in North America
├── Shard 2  (eu)   ── clients in Europe
└── ...             ── up to `nodes` shards per channel
Each shard is a Durable Object instance. createPubSubServer handles shard selection, location weighting, and routing — you configure the number of nodes and the location distribution in a single options object.

Installation

npm install partyserver partysub partysocket

Server setup

Minimal server

// src/server.ts
import { createPubSubServer } from "partysub/server";

const { PubSubServer, routePubSubRequest } = createPubSubServer({
  binding: "PubSub", // must match the binding name in wrangler.jsonc
  nodes: 100,        // number of shards per channel
});

// Export the DO class so Cloudflare can bind it
export { PubSubServer };

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const pubSubResponse = await routePubSubRequest(request, env);
    return pubSubResponse || new Response("Not found", { status: 404 });
  },
} satisfies ExportedHandler<Env>;

With location weighting

// src/server.ts
import { createPubSubServer } from "partysub/server";

const { PubSubServer, routePubSubRequest } = createPubSubServer({
  binding: "PubSub",
  nodes: 100,

  locations: {
    // Weight determines how many of the 100 nodes are spun up in each region.
    // Possible values: https://developers.cloudflare.com/durable-objects/reference/data-location/#provide-a-location-hint
    eu: 1,    // 1 % of nodes in Europe
    wnam: 3,  // 3 % in Western North America
    // Clients connecting from unlisted regions are routed to a random node
  },
});

export { PubSubServer };

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    return (
      (await routePubSubRequest(request, env)) ||
      new Response("Not found", { status: 404 })
    );
  },
} satisfies ExportedHandler<Env>;

Wrangler configuration

// wrangler.jsonc
{
  "name": "my-pubsub-app",
  "main": "src/server.ts",
  "durable_objects": {
    "bindings": [
      {
        "name": "PubSub",
        "class_name": "PubSubServer"
      }
    ]
  },
  "migrations": [
    {
      "tag": "v1",
      "new_sqlite_classes": ["PubSubServer"]
    }
  ]
}
The binding value passed to createPubSubServer must exactly match the name field in durable_objects.bindings. The class_name must match the exported class name (PubSubServer).

Client setup

Connecting and subscribing to topics

Pass a topics query parameter to subscribe to specific topics or wildcard patterns. Without topics, the client receives all messages on the channel.
// src/client.ts
import { PartySocket } from "partysocket";

const ws = new PartySocket({
  host: "https://my-pubsub-app.workers.dev", // defaults to window.location.host
  party: "pub-sub", // lowercase binding name
  room: "default",  // channel name
  query: {
    topics: [
      "breaking-news",  // subscribe to an exact topic
      "sports:*",       // subscribe to all topics with the "sports:" prefix
    ],
  },
});

// Receive messages
ws.addEventListener("message", (event) => {
  const { topic, data } = JSON.parse(event.data as string);
  console.log(`[${topic}]`, data);
});

Publishing via WebSocket

// Send a message — it will be fanned out to all subscribers of "breaking-news"
ws.send(JSON.stringify({ topic: "breaking-news", data: "Hello world!" }));

Publishing via HTTP POST

You can publish from any Cloudflare Worker or server-side process without holding a WebSocket connection:
import { PartySocket } from "partysocket";

await PartySocket.fetch(
  {
    host: "https://my-pubsub-app.workers.dev",
    party: "pub-sub",
    room: "default",
  },
  {
    method: "POST",
    body: JSON.stringify({ topic: "breaking-news", data: "Server-side update" }),
  }
);

React integration

// src/App.tsx
import { useState } from "react";
import { usePartySocket } from "partysocket/react";

type PubSubMessage = { topic: string; data: string };

export default function App() {
  const [messages, setMessages] = useState<PubSubMessage[]>([]);

  const socket = usePartySocket({
    party: "pub-sub",
    room: "default",
    query: {
      topics: ["breaking-news", "sports:*"],
    },
    onMessage(evt) {
      const msg = JSON.parse(evt.data as string) as PubSubMessage;
      setMessages((prev) => [...prev, msg]);
    },
  });

  function publish(topic: string, data: string) {
    socket.send(JSON.stringify({ topic, data }));
  }

  return (
    <div>
      <button onClick={() => publish("breaking-news", "Hello!")}>Publish</button>
      <ul>
        {messages.map((m, i) => (
          <li key={i}>
            <b>[{m.topic}]</b> {m.data}
          </li>
        ))}
      </ul>
    </div>
  );
}

Topic patterns

Topics are plain strings. partysub supports prefix wildcard matching with * at the end:
PatternMatches
"breaking-news"Exactly the breaking-news topic
"sports:*"Any topic starting with sports: — e.g. sports:football, sports:nba
Use namespaced topics (category:subtopic) to keep channels organised and to let clients subscribe selectively without receiving every message on a busy channel.

Full fixture reference

The fixtures/pubsub directory in the PartyKit repository contains a complete working example with the server, React client, and wrangler.jsonc configuration. It demonstrates both WebSocket and HTTP POST publishing, and shows how usePartySocket handles incoming topic-keyed messages.

Build docs developers (and LLMs) love