Use WebSockets·续

this.ctx.acceptWebSocket(server);

// Persist per-connection state that survives hibernation const state = { orderId, joinedAt: Date.now(), }; server.serializeAttachment(state);

return new Response(null, { status: 101, webSocket: client }); }

async webSocketMessage(ws, message) { // Restore state after potential hibernation const state = ws.deserializeAttachment(); ws.send(Hello ${state.orderId}, you joined at ${state.joinedAt}); }

async webSocketClose(ws, code, reason, wasClean) { const state = ws.deserializeAttachment(); console.log(${state.orderId} disconnected); // With web_socket_auto_reply_to_close (compat date >= 2026-04-07), the runtime // auto-replies to Close frames. Calling close() is safe but no longer required. ws.close(code, reason); } }


```ts
import { DurableObject } from "cloudflare:workers";

interface ConnectionState {
orderId: string;
joinedAt: number;
}

export class WebSocketServer extends DurableObject<Env> {
    async fetch(request: Request): Promise<Response> {
        const url = new URL(request.url);
        const orderId = url.searchParams.get("orderId") ?? "anonymous";

const webSocketPair = new WebSocketPair();
        const [client, server] = Object.values(webSocketPair);

this.ctx.acceptWebSocket(server);

// Persist per-connection state that survives hibernation
        const state: ConnectionState = {
            orderId,
            joinedAt: Date.now(),
        };
        server.serializeAttachment(state);

return new Response(null, { status: 101, webSocket: client });
    }

async webSocketMessage(ws: WebSocket, message: string | ArrayBuffer) {
        // Restore state after potential hibernation
        const state = ws.deserializeAttachment() as ConnectionState;
        ws.send(`Hello ${state.orderId}, you joined at ${state.joinedAt}`);
    }

async webSocketClose(ws: WebSocket, code: number, reason: string, wasClean: boolean) {
        const state = ws.deserializeAttachment() as ConnectionState;
        console.log(`${state.orderId} disconnected`);
        // With web_socket_auto_reply_to_close (compat date >= 2026-04-07), the runtime
        // auto-replies to Close frames. Calling close() is safe but no longer required.
        ws.close(code, reason);
    }

}

WebSocket Standard API

WebSocket connections are established by making an HTTP GET request with the Upgrade: websocket header.

The typical flow:

  1. A Worker validates the upgrade request
  2. The Worker proxies the request to the Durable Object
  3. The Durable Object accepts the server side connection
  4. The Worker returns the client side connection in the response

Validate requests in a Worker

Both Workers and Durable Objects are billed based on the number of requests. Validate requests in your Worker to avoid billing for invalid requests against a Durable Object.

// Worker
export default {
    async fetch(request, env, ctx) {
        if (request.method === "GET" && request.url.endsWith("/websocket")) {
            // Expect to receive a WebSocket Upgrade request.
            // If there is one, accept the request and return a WebSocket Response.
            const upgradeHeader = request.headers.get("Upgrade");
            if (!upgradeHeader || upgradeHeader !== "websocket") {
                return new Response(null, {
                    status: 426,
                    statusText: "Durable Object expected Upgrade: websocket",
                    headers: {
                        "Content-Type": "text/plain",
                    },
                });
            }

// This example will refer to a single Durable Object instance, since the name "foo" is
            // hardcoded
            let stub = env.WEBSOCKET_SERVER.getByName("foo");

// The Durable Object's fetch handler will accept the server side connection and return
            // the client
            return stub.fetch(request);
        }

return new Response(null, {
            status: 400,
            statusText: "Bad Request",
            headers: {
                "Content-Type": "text/plain",
            },
        });
    },
};
// Worker
export default {
    async fetch(request, env, ctx): Promise<Response> {
        if (request.method === "GET" && request.url.endsWith("/websocket")) {
            // Expect to receive a WebSocket Upgrade request.
            // If there is one, accept the request and return a WebSocket Response.
            const upgradeHeader = request.headers.get("Upgrade");
            if (!upgradeHeader || upgradeHeader !== "websocket") {
                return new Response(null, {
                    status: 426,
                    statusText: "Durable Object expected Upgrade: websocket",
                    headers: {
                        "Content-Type": "text/plain",
                    },
                });
            }

// This example will refer to a single Durable Object instance, since the name "foo" is
            // hardcoded
            let stub = env.WEBSOCKET_SERVER.getByName("foo");

// The Durable Object's fetch handler will accept the server side connection and return
            // the client
            return stub.fetch(request);
        }

return new Response(null, {
            status: 400,
            statusText: "Bad Request",
            headers: {
                "Content-Type": "text/plain",
            },
        });
    },
} satisfies ExportedHandler<Env>;
from workers import Response, WorkerEntrypoint

# Worker

class Default(WorkerEntrypoint):
async def fetch(self, request):
if request.method == "GET" and request.url.endswith("/websocket"): # Expect to receive a WebSocket Upgrade request. # If there is one, accept the request and return a WebSocket Response.
upgrade_header = request.headers.get("Upgrade")
if not upgrade_header or upgrade_header != "websocket":
return Response(
None,
status=426,
status_text="Durable Object expected Upgrade: websocket",
headers={
"Content-Type": "text/plain",
},
)

# This example will refer to a single Durable Object instance, since the name "foo" is
            # hardcoded
            stub = self.env.WEBSOCKET_SERVER.getByName("foo")

# The Durable Object's fetch handler will accept the server side connection and return
            # the client
            return await stub.fetch(request)

return Response(
            None,
            status=400,
            status_text="Bad Request",
            headers={
                "Content-Type": "text/plain",
            },
        )

The following Durable Object creates a WebSocket connection and responds to messages with the total number of connections:

import { DurableObject } from "cloudflare:workers";

// Durable Object
export class WebSocketServer extends DurableObject {
    currentlyConnectedWebSockets;

constructor(ctx, env) {
        super(ctx, env);
        this.currentlyConnectedWebSockets = 0;
    }

async fetch(request) {
        // Creates two ends of a WebSocket connection.
        const webSocketPair = new WebSocketPair();
        const [client, server] = Object.values(webSocketPair);

// Calling `accept()` connects the WebSocket to this Durable Object
        server.accept();
        this.currentlyConnectedWebSockets += 1;

// Upon receiving a message from the client, the server replies with the same message,
        // and the total number of connections with the "[Durable Object]: " prefix
        server.addEventListener("message", (event) => {
            server.send(
                `[Durable Object] currentlyConnectedWebSockets: ${this.currentlyConnectedWebSockets}`,
            );
        });

// When the client closes the connection, clean up the server side.
        // With web_socket_auto_reply_to_close (compat date >= 2026-04-07), the runtime
        // auto-replies to Close frames. Calling close() is safe but no longer required.
        server.addEventListener("close", (cls) => {
            this.currentlyConnectedWebSockets -= 1;
            server.close(cls.code, "Durable Object is closing WebSocket");
        });

return new Response(null, {
            status: 101,
            webSocket: client,
        });
    }
}
// Durable Object
export class WebSocketServer extends DurableObject {
    currentlyConnectedWebSockets: number;

constructor(ctx: DurableObjectState, env: Env) {
        super(ctx, env);
        this.currentlyConnectedWebSockets = 0;
    }

async fetch(request: Request): Promise<Response> {
        // Creates two ends of a WebSocket connection.
        const webSocketPair = new WebSocketPair();
        const [client, server] = Object.values(webSocketPair);

// Calling `accept()` connects the WebSocket to this Durable Object
        server.accept();
        this.currentlyConnectedWebSockets += 1;

// Upon receiving a message from the client, the server replies with the same message,
        // and the total number of connections with the "[Durable Object]: " prefix
        server.addEventListener("message", (event: MessageEvent) => {
            server.send(
                `[Durable Object] currentlyConnectedWebSockets: ${this.currentlyConnectedWebSockets}`,
            );
        });

// When the client closes the connection, clean up the server side.
        // With web_socket_auto_reply_to_close (compat date >= 2026-04-07), the runtime
        // auto-replies to Close frames. Calling close() is safe but no longer required.
        server.addEventListener("close", (cls: CloseEvent) => {
            this.currentlyConnectedWebSockets -= 1;
            server.close(cls.code, "Durable Object is closing WebSocket");
        });

return new Response(null, {
            status: 101,
            webSocket: client,
        });
    }
}
from workers import Response, DurableObject
from js import WebSocketPair
from pyodide.ffi import create_proxy

# Durable Object

class WebSocketServer(DurableObject):
def **init**(self, ctx, env):
super().**init**(ctx, env)
self.currently_connected_websockets = 0

async def fetch(self, request):
        # Creates two ends of a WebSocket connection.
        client, server = WebSocketPair.new().object_values()

# Calling `accept()` connects the WebSocket to this Durable Object
        server.accept()
        self.currently_connected_websockets += 1

# Upon receiving a message from the client, the server replies with the same message,
        # and the total number of connections with the "[Durable Object]: " prefix
        def on_message(event):
            server.send(
                f"[Durable Object] currentlyConnectedWebSockets: {self.currently_connected_websockets}"
            )

server.addEventListener("message", create_proxy(on_message))

# When the client closes the connection, clean up the server side.
        # With web_socket_auto_reply_to_close (compat date >= 2026-04-07), the runtime
        # auto-replies to Close frames. Calling close() is safe but no longer required.
        def on_close(event):
            self.currently_connected_websockets -= 1
            server.close(event.code, "Durable Object is closing WebSocket")

server.addEventListener("close", create_proxy(on_close))

return Response(
            None,
            status=101,
            web_socket=client,
        )

Configure your Wrangler file with a Durable Object binding and migration:

{
    "$schema": "./node_modules/wrangler/config-schema.json",
    "name": "websocket-server",
    "durable_objects": {
        "bindings": [
            {
                "name": "WEBSOCKET_SERVER",
                "class_name": "WebSocketServer"
            }
        ]
    },
    "migrations": [
        {
            "tag": "v1",
            "new_sqlite_classes": ["WebSocketServer"]
        }
    ]
}
"$schema" = "./node_modules/wrangler/config-schema.json"
name = "websocket-server"

[[durable_objects.bindings]]
name = "WEBSOCKET_SERVER"
class_name = "WebSocketServer"

[[migrations]]
tag = "v1"
new_sqlite_classes = [ "WebSocketServer" ]

A full example is available in Build a WebSocket server.

WebSocket disconnection on deploy

Code updates disconnect all WebSockets. Deploying a new version restarts every Durable Object, which disconnects any existing connections.


Was this helpful?

YesNo

On this page

Use WebSockets·续 - 图1Docs

{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/durable-objects/best-practices/websockets/#page","headline":"Use WebSockets","description":"Serve WebSocket connections from Durable Objects, including the standard and Hibernation APIs.","url":"https://developers.cloudflare.com/durable-objects/best-practices/websockets/","inLanguage":"en","image":"https://developers.cloudflare.com/durable-objects/best-practices/websockets/og.png?v=f6ee405dcdefa97c","dateModified":"2026-09-30","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}


description: Schedule future wake-ups for Durable Objects using the Alarms API with guaranteed at-least-once execution. title: Alarms image: https://developers.cloudflare.com/durable-objects/api/alarms/og.png?v=11f204548f3814f4


Skip to content

Documentation Index
Fetch the complete documentation index at: https://developers.cloudflare.com/durable-objects/llms.txt
Use this file to discover all available pages before exploring further.