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 PartyServer application needs a way to direct incoming HTTP and WebSocket requests to the right Durable Object instance. The routePartykitRequest function handles that mapping automatically — it parses the URL, finds the matching DO namespace in your env, and forwards the request, including performing the WebSocket upgrade. You configure hooks to intercept requests before they arrive at the server, making routing the natural place to enforce authentication, rewrite headers, or apply data-residency rules.

How URL matching works

routePartykitRequest expects URLs of the form:
/<prefix>/:server/:name
  • prefix — defaults to "parties". Change it with the prefix option.
  • :server — matched case-insensitively against the kebab-case form of a DO binding name in env (e.g. the binding MyServer matches the segment my-server).
  • :name — the room or instance name passed to idFromName().
If the URL does not match, routePartykitRequest returns null, so you can fall through to other handlers.

Basic setup

// index.ts
import { routePartykitRequest, Server } from "partyserver";

export class MyServer extends Server {
  onConnect(connection) {
    this.broadcast(`${connection.id} joined`);
  }

  onMessage(connection, message) {
    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>;
Configure wrangler.jsonc to declare the binding and migration:
{
  "name": "my-app",
  "main": "index.ts",
  "durable_objects": {
    "bindings": [
      {
        "name": "MyServer",
        "class_name": "MyServer"
      }
    ]
  },
  "migrations": [
    {
      "tag": "v1",
      "new_sqlite_classes": ["MyServer"]
    }
  ]
}
With this in place, a WebSocket connection to /parties/my-server/room-42 will land in MyServer with this.name === "room-42".

Custom prefix

Set prefix to mount your servers at a different path:
await routePartykitRequest(request, env, { prefix: "rooms" });
// now matches /rooms/:server/:name
The prefix can also be a multi-segment path like "api/v1/parties".

Intercepting requests with hooks

routePartykitRequest exposes two hook options that run before the request reaches the Durable Object:
HookFires for
onBeforeConnectWebSocket upgrade requests
onBeforeRequestPlain HTTP requests
Both hooks receive the current Request and a lobby object with className (the DO binding name) and name (the room name). They can return:
  • A Request to substitute a modified request.
  • A Response to short-circuit the routing entirely (e.g. return 401).
  • undefined / void to pass the request through unchanged.

Example: auth checking in onBeforeConnect

import { routePartykitRequest } from "partyserver";

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    return (
      (await routePartykitRequest(request, env, {
        async onBeforeConnect(req, lobby) {
          const token = new URL(req.url).searchParams.get("token");
          if (!token) {
            return new Response("Unauthorized", { status: 401 });
          }

          // Attach verified identity as a custom header
          // so the Server can read it in onConnect
          const next = new Request(req);
          next.headers.set("x-user-token", token);
          return next;
        }
      })) || new Response("Not Found", { status: 404 })
    );
  }
} satisfies ExportedHandler<Env>;
Auth must be enforced here — once the WebSocket upgrade completes the connection is established and cannot be rejected from inside onConnect.

Directly addressing a server with getServerByName

When you need a stub to a specific DO instance outside of the normal routing path — for example, to send an RPC from one server to another — use getServerByName:
import { getServerByName, Server } from "partyserver";

export class CoordinatorServer extends Server {
  async onMessage(connection, message) {
    // Get a stub for a sibling server instance
    const peer = await getServerByName(this.env.MyServer, "room-42");
    // Call RPC methods on the stub
    await peer.fetch(new Request("https://internal/notify"));
  }
}
getServerByName resolves the name via idFromName, awaits onStart() on the target before returning the stub, and supports the same locationHint, jurisdiction, and routingRetry options as routePartykitRequest.

Geographic routing and data residency

locationHint

Suggests a Cloudflare region for the Durable Object. Best-effort, not guaranteed. Values: "wnam", "enam", "sam", "weur", "eeur", "apac", "oc", "afr", "me".

jurisdiction

Hard constraint that restricts a DO to a legal jurisdiction, such as "eu" for GDPR compliance. Cannot be combined with per-location weights.
await routePartykitRequest(request, env, {
  locationHint: "weur",
  jurisdiction: "eu"
});

Retry options for transient errors

Durable Object routing can occasionally fail with transient infrastructure errors. routingRetry (enabled by default with 3 attempts and jittered exponential backoff) handles these automatically. Only errors with retryable === true are retried; overloaded errors are never retried.
await routePartykitRequest(request, env, {
  routingRetry: {
    maxAttempts: 5,
    baseDelayMs: 200,
    maxDelayMs: 2000,
    onRetry({ attempt, delayMs, error }) {
      console.warn(`Retry attempt ${attempt} after ${delayMs}ms:`, error);
    }
  }
});
Pass routingRetry: false to disable retries entirely.

CORS support

Enable CORS for all matched routes by passing cors: true for permissive defaults, or supply explicit headers for credentialed requests:
await routePartykitRequest(request, env, {
  cors: {
    "Access-Control-Allow-Origin": "https://myapp.com",
    "Access-Control-Allow-Credentials": "true",
    "Access-Control-Allow-Methods": "GET, POST, HEAD, OPTIONS",
    "Access-Control-Allow-Headers": "Content-Type, Authorization"
  }
});
Preflight OPTIONS requests are handled automatically for matched routes.

Build docs developers (and LLMs) love