---
title: Use WebSockets · Cloudflare Durable Objects docs
description: This guide covers how to use Durable Objects as WebSocket servers
  that can connect thousands of clients (per instance), as well as a WebSocket
  client to connect to other servers or even Durable Objects.
lastUpdated: 2025-09-24T13:21:38.000Z
chatbotDeprioritize: false
source_url:
  html: https://developers.cloudflare.com/durable-objects/best-practices/websockets/
  md: https://developers.cloudflare.com/durable-objects/best-practices/websockets/index.md
---

This guide covers how to use Durable Objects as WebSocket servers that can connect thousands of clients (per instance), as well as a WebSocket client to connect to other servers or even Durable Objects.

There are two sets of WebSockets API:

1. **Native Durable Object WebSocket API**, which allows your Durable Object to hibernate without disconnecting clients when not actively doing work **(recommended)**.
2. **Web Standard WebSocket APIs**, using the familiar `addEventListener` event pattern.

## What are WebSockets?

WebSockets are long-lived TCP connections that enable bi-directional, real-time communication between client and server. Both Cloudflare Durable Objects and Workers can act as WebSocket endpoints - either as a client or as a server. Because WebSocket sessions are long-lived, applications commonly use Durable Objects to accept either the client or server connection. While there are other use cases for using Workers exclusively with WebSockets, for example proxying WebSocket messages, WebSockets are most useful when combined with Durable Objects.

Because Durable Objects provide a single-point-of-coordination between [Cloudflare Workers](https://developers.cloudflare.com/workers/), a single Durable Object instance can be used in parallel with WebSockets to coordinate between multiple clients, such as participants in a chat room or a multiplayer game. Refer to [Cloudflare Edge Chat Demo](https://github.com/cloudflare/workers-chat-demo) for an example of using Durable Objects with WebSockets.

Both Cloudflare Durable Objects and Workers can use the [Web Standard WebSocket API](https://developers.cloudflare.com/workers/runtime-apis/websockets/) to build applications, but a major differentiator of Cloudflare Durable Objects relative to other platforms is the ability to Hibernate WebSocket connections to save costs. Clients can remain connected when the Durable Object is idle (and not sending messages or running compute tasks), which allows you to push events to clients and minimize the active duration (GB-seconds) associated with long-running Durable Object processes.

## Durable Objects' Hibernation WebSocket API

In addition to [the Web Standard WebSocket API](https://developers.cloudflare.com/workers/runtime-apis/websockets/), Cloudflare Durable Objects can use the WebSocket Hibernation API which extends the Web Standard WebSocket API to reduce costs. Specifically, [billable Duration (GB-s) charges](https://developers.cloudflare.com/durable-objects/platform/pricing/) are not incurred during periods of inactivity.

### How does Durable Object Hibernation work with WebSockets?

When a Durable Object receives no events (like alarms) or messages for a short period of time, the Durable Object is evicted from memory to avoid unnecessary charges (refer to [Lifecycle of a Durable Object](https://developers.cloudflare.com/durable-objects/concepts/durable-object-lifecycle/)). The WebSocket clients remain connected to the Cloudflare network. When your Durable Object receives an event during hibernation, it is re-initialized, its `constructor` function is called, and it can access the WebSocket clients with the `this.ctx.getWebSockets()` function.

When the Durable Object is evicted from memory, its in-memory state is reset. It is common to rely on in-memory state to organize your WebSockets (for example, keeping your WebSockets in rooms with a `Map<WebSocket, Object>` data type). With Hibernation, you must restore the in-memory state of your Durable Object within the `constructor` function.

To do this, you can use the [`serializeAttachment`](#websocketserializeattachment) to persist additional data with the Durable Object WebSocket class, which will persist the data to the Durable Object's storage. Upon re-initialization of the Durable Object, you can access this data with [`deserializeAttachment`](#websocketdeserializeattachment).

The Durable Object WebSocket class consists of Cloudflare-specific extensions to the Web Standard WebSocket API. These extensions are either present on the [DurableObjectState](https://developers.cloudflare.com/durable-objects/api/state) interface, or as handler methods on the Durable Object class.

Note

Hibernation is only supported when a Durable Object acts as a WebSocket server. Currently, outgoing WebSockets cannot hibernate.

Events, for example [alarms](https://developers.cloudflare.com/durable-objects/api/alarms/), incoming requests, and scheduled callbacks using `setTimeout/setInterval`, can prevent a Durable Object from being inactive and therefore prevent this cost saving. Read more in the section [When does a Durable Object incur duration charges?](https://developers.cloudflare.com/durable-objects/platform/pricing/#when-does-a-durable-object-incur-duration-charges).

### Example

To use WebSockets with Durable Objects, you first need to proxy the `request` object from the Worker to the Durable Object, as is done in the [WebSocket Standard API example](https://developers.cloudflare.com/durable-objects/examples/websocket-server/). Using the Hibernation WebSockets API in Durable Objects differs slightly from using WebSocket Standard APIs. In summary, [`DurableObjectState::acceptWebSocket`](https://developers.cloudflare.com/durable-objects/api/state/#acceptwebsocket) is called to accept the server side of the WebSocket connection, and handler methods are defined on the Durable Object class for relevant event types rather than adding event listeners.

If an event occurs for a hibernated Durable Object's corresponding handler method, it will return to memory. This will call the Durable Object's constructor, so it is best to minimize work in the constructor when using WebSocket hibernation.

* JavaScript

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


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


      // Calling `acceptWebSocket()` connects the WebSocket to the Durable Object, allowing the WebSocket to send and receive messages.
      // Unlike `ws.accept()`, `state.acceptWebSocket(ws)` allows the Durable Object to be hibernated
      // When the Durable Object receives a message during Hibernation, it will run the `constructor` to be re-initialized
      this.ctx.acceptWebSocket(server);


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


    async webSocketMessage(ws, message) {
      // Upon receiving a message from the client, reply with the same message,
      // but will prefix the message with "[Durable Object]: " and return the number of connections.
      ws.send(
        `[Durable Object] message: ${message}, connections: ${this.ctx.getWebSockets().length}`,
      );
    }


    async webSocketClose(ws, code, reason, wasClean) {
      // If the client closes the connection, the runtime will invoke the webSocketClose() handler.
      ws.close(code, "Durable Object is closing WebSocket");
    }
  }
  ```

* TypeScript

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


  export interface Env {
    WEBSOCKET_HIBERNATION_SERVER: DurableObjectNamespace<WebSocketHibernationServer>;
  }


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


      // Calling `acceptWebSocket()` connects the WebSocket to the Durable Object, allowing the WebSocket to send and receive messages.
      // Unlike `ws.accept()`, `state.acceptWebSocket(ws)` allows the Durable Object to be hibernated
      // When the Durable Object receives a message during Hibernation, it will run the `constructor` to be re-initialized
      this.ctx.acceptWebSocket(server);


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


    async webSocketMessage(ws: WebSocket, message: ArrayBuffer | string) {
      // Upon receiving a message from the client, reply with the same message,
      // but will prefix the message with "[Durable Object]: " and return the number of connections.
      ws.send(
        `[Durable Object] message: ${message}, connections: ${this.ctx.getWebSockets().length}`,
      );
    }


    async webSocketClose(
      ws: WebSocket,
      code: number,
      reason: string,
      wasClean: boolean,
    ) {
      // If the client closes the connection, the runtime will invoke the webSocketClose() handler.
      ws.close(code, "Durable Object is closing WebSocket");
    }
  }
  ```

Similar to the [WebSocket Standard API example](https://developers.cloudflare.com/durable-objects/examples/websocket-server/), to execute this code, configure your Wrangler file to include a Durable Object [binding](https://developers.cloudflare.com/durable-objects/get-started/#4-configure-durable-object-bindings) and [migration](https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/) based on the namespace and class name chosen previously.

* wrangler.jsonc

  ```jsonc
  {
    "name": "websocket-hibernation-server",
    "durable_objects": {
      "bindings": [
        {
          "name": "WEBSOCKET_HIBERNATION_SERVER",
          "class_name": "WebSocketHibernationServer"
        }
      ]
    },
    "migrations": [
      {
        "tag": "v1",
        "new_sqlite_classes": [
          "WebSocketHibernationServer"
        ]
      }
    ]
  }
  ```

* wrangler.toml

  ```toml
  name = "websocket-hibernation-server"


  [[durable_objects.bindings]]
  name = "WEBSOCKET_HIBERNATION_SERVER"
  class_name = "WebSocketHibernationServer"


  [[migrations]]
  tag = "v1"
  new_sqlite_classes = ["WebSocketHibernationServer"]
  ```

A full example can be found in [Build a WebSocket server with WebSocket Hibernation](https://developers.cloudflare.com/durable-objects/examples/websocket-hibernation-server/).

Support for local development

Prior to `wrangler@3.13.2` and Miniflare `v3.20231016.0`, WebSockets did not hibernate when using local development environments such as `wrangler dev` or Miniflare.

If you are using older versions, note that while hibernatable WebSocket events such as [`webSocketMessage()`](https://developers.cloudflare.com/durable-objects/api/base/#websocketmessage) will still be delivered, the Durable Object will never be evicted from memory.

### Extended methods

The following are methods available on the **Native Durable Object WebSocket API**, the WebSocket class available in Durable Objects. These methods facilitate persisting state to storage to set and restore state before and after a Durable Object's hibernation.

#### `WebSocket.serializeAttachment()`

* `serializeAttachment(value any)`: void

  * Keeps a copy of `value` associated with the WebSocket to survive hibernation. The value can be any type supported by the [structured clone algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm), which is true of most types. If the value needs to be durable please use [Durable Object Storage](https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/).

  * If you modify `value` after calling this method, those changes will not be retained unless you call this method again. The serialized size of `value` is limited to 2,048 bytes, otherwise this method will throw an error. If you need larger values to survive hibernation, use the [Storage API](https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/) and pass the corresponding key to this method so it can be retrieved later.

#### `WebSocket.deserializeAttachment()`

* `deserializeAttachment()`: any

  * Retrieves the most recent value passed to `serializeAttachment()`, or `null` if none exists.

## WebSocket Standard API

WebSocket connections are established by making an HTTP GET request with the `Upgrade: websocket` header. A Cloudflare Worker is commonly used to validate the request, proxy the request to the Durable Object to accept the server side connection, and return the client side connection in the response.

Validate requests in a Worker

Both Workers and Durable Objects are billed, in part, based on the number of requests they receive. To avoid being billed for requests against a Durable Object for invalid requests, be sure to validate requests in your Worker.

* JavaScript

  ```js
  // 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",
        },
      });
    },
  };
  ```

* TypeScript

  ```ts
  // 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>;
  ```

Each WebSocket server in this example is represented by a Durable Object. This WebSocket server creates a single WebSocket connection and responds to all messages over that connection with the total number of accepted WebSocket connections. In the Durable Object's fetch handler we create client and server connections and add event listeners for relevant event types.

* JavaScript

  ```js
  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}`,
        );
      });


      // If the client closes the connection, the runtime will close the connection too.
      server.addEventListener("close", (cls) => {
        this.currentlyConnectedWebSockets -= 1;
        server.close(cls.code, "Durable Object is closing WebSocket");
      });


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

* TypeScript

  ```ts
  // 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}`,
        );
      });


      // If the client closes the connection, the runtime will close the connection too.
      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,
      });
    }
  }
  ```

To execute this code, configure your Wrangler file to include a Durable Object [binding](https://developers.cloudflare.com/durable-objects/get-started/#4-configure-durable-object-bindings) and [migration](https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/) based on the namespace and class name chosen previously.

* wrangler.jsonc

  ```jsonc
  {
    "name": "websocket-server",
    "durable_objects": {
      "bindings": [
        {
          "name": "WEBSOCKET_SERVER",
          "class_name": "WebSocketServer"
        }
      ]
    },
    "migrations": [
      {
        "tag": "v1",
        "new_sqlite_classes": [
          "WebSocketServer"
        ]
      }
    ]
  }
  ```

* wrangler.toml

  ```toml
  name = "websocket-server"


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


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

A full example can be found in [Build a WebSocket server](https://developers.cloudflare.com/durable-objects/examples/websocket-server/).

WebSockets disconnection

Code updates will disconnect all WebSockets. If you deploy a new version of a Worker, every Durable Object is restarted. Any connections to old Durable Objects will be disconnected.

## Related resources

* [Mozilla Developer Network's (MDN) documentation on the WebSocket class](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
* [Cloudflare's WebSocket template for building applications on Workers using WebSockets](https://github.com/cloudflare/websocket-template)
* [Durable Object base class](https://developers.cloudflare.com/durable-objects/api/base/)
* [Durable Object State interface](https://developers.cloudflare.com/durable-objects/api/state/)
