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.

If you built an app on the original PartyKit.io hosted platform, you can migrate it to run on your own Cloudflare Workers account using the open-source partyserver and partysocket packages. The server-side API is intentionally similar — most lifecycle hooks keep the same names — but there are a handful of structural differences to be aware of.
The original PartyKit.io platform and these open-source libraries (partyserver, partysocket, y-partyserver, etc.) are separate projects. The libraries let you host the same real-time patterns on your own Cloudflare account without depending on the PartyKit.io service.

Key differences

1. URL routing is decoupled

PartyKit.io automatically inferred Durable Object routing from the URL pattern /parties/:server/:room. partyserver does not do this by default — the URL shape and the DO namespace are decoupled. To get the same /parties/:server/:name routing you are used to, call routePartykitRequest in your Worker’s fetch handler:
import { routePartykitRequest } from "partyserver";

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    return (
      (await routePartykitRequest(request, env)) ||
      new Response("Not Found", { status: 404 })
    );
  }
};
routePartykitRequest matches requests of the form /${prefix}/:server/:name (the prefix defaults to "parties") and routes them to the Durable Object namespace whose binding name matches :server (case-insensitive).

2. No auto-inferred Durable Object declarations

PartyKit.io inferred Durable Object bindings and migrations from your source code. With partyserver you must declare them manually in wrangler.jsonc:
{
  "durable_objects": {
    "bindings": [
      { "name": "MyServer", "class_name": "MyServer" }
    ]
  },
  "migrations": [
    { "tag": "v1", "new_sqlite_classes": ["MyServer"] }
  ]
}

3. No built-in AI, KV, or static asset bindings

PartyKit.io provided shorthand configuration for AI, KV, R2, and static asset bindings. In a standard Cloudflare Worker you add these through wrangler.jsonc’s native binding sections ([[kv_namespaces]], [[r2_buckets]], [ai], etc.) and then access them via this.env inside your server.

4. The server name comes from ctx.id.name

In partyserver, this.name on a Server instance resolves from the underlying Durable Object’s ctx.id.name. This is populated whenever the DO is addressed via idFromName() — which is the normal path taken by routePartykitRequest.

Migration steps

1

Install partyserver and partysocket

npm install partyserver partysocket
2

Update your server class

Change extends Party (the PartyKit.io class) to extends Server from partyserver. The lifecycle hook names are the same.
// Before (PartyKit.io)
import type { Party, PartyServer, Connection } from "partykit/server";

export default class MyServer implements PartyServer {
  constructor(readonly party: Party) {}

  onConnect(connection: Connection) {
    console.log("connected", connection.id);
  }

  onMessage(connection: Connection, message: string) {
    this.party.broadcast(message, [connection.id]);
  }
}
// After (partyserver)
import { Server } from "partyserver";
import type { Connection } from "partyserver";

export class MyServer extends Server {
  onConnect(connection: Connection) {
    console.log("Connected", connection.id, "to server", this.name);
  }

  onMessage(connection: Connection, message: string) {
    this.broadcast(message, [connection.id]);
  }
}
Key changes:
  • Extend Server as a class (no implements interface or constructor injection)
  • Use this.name instead of this.party.id
  • Use this.broadcast(...) instead of this.party.broadcast(...)
  • Use this.env instead of this.party.env
3

Add a fetch handler with routePartykitRequest

Add a default export with a fetch handler that delegates to routePartykitRequest. This replaces PartyKit.io’s built-in URL routing.
// index.ts
import { routePartykitRequest, Server } from "partyserver";
import type { Connection } from "partyserver";

export class MyServer extends Server {
  onConnect(connection: Connection) {
    console.log("Connected", connection.id, "to server", this.name);
  }

  onMessage(connection: Connection, message: string) {
    this.broadcast(message, [connection.id]);
  }
}

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

Configure wrangler.jsonc

Add the Durable Object binding and a migration for each server class.
{
  "name": "my-app",
  "main": "index.ts",
  "durable_objects": {
    "bindings": [
      {
        "name": "MyServer",
        "class_name": "MyServer"
      }
    ]
  },
  "migrations": [
    {
      "tag": "v1",
      "new_sqlite_classes": ["MyServer"]
    }
  ]
}
The binding name must match the class name exactly (case-sensitive). routePartykitRequest matches the URL segment :server case-insensitively, so a request to /parties/my-server/room-1 will route to the MyServer binding.
5

Update client-side code

The partysocket package is a drop-in replacement for the partysocket package that PartyKit.io shipped. The import path is partysocket and the API is identical.
// Before (PartyKit.io bundled partysocket)
import PartySocket from "partysocket";

const socket = new PartySocket({
  host: "my-app.my-user.partykit.dev",
  room: "my-room",
});
// After (same API, different host)
import PartySocket from "partysocket";

const socket = new PartySocket({
  host: "my-app.my-account.workers.dev", // your Workers URL
  room: "my-room",
  party: "my-server",  // kebab-cased class name
});
If you were using the React hook:
// Same import, same API
import { usePartySocket } from "partysocket/react";

const socket = usePartySocket({
  host: "my-app.my-account.workers.dev",
  room: "my-room",
  party: "my-server",
});

Lifecycle hook compatibility

All standard PartyKit.io lifecycle hooks are available in partyserver with the same names:
HookDescription
onStart()Called when the server starts or wakes from hibernation
onConnect(connection, context)New WebSocket connection established
onMessage(connection, message)Message received from a client
onClose(connection, code, reason, wasClean)Client closed the connection
onError(connection, error)Error on a connection
onRequest(request)HTTP request to the server
onAlarm()Durable Object alarm fired

URL compatibility

routePartykitRequest reproduces the same /parties/:server/:name URL pattern that PartyKit.io used. No changes to client URL construction are required as long as you pass the same host, party, and room values. If you need a different URL prefix (e.g. /rooms/) you can configure it:
await routePartykitRequest(request, env, { prefix: "rooms" });

Build docs developers (and LLMs) love