Skip to content
Cloudflare Docs

WebSockets

Agents support WebSocket connections for real-time, bi-directional communication. This page covers server-side WebSocket handling. For client-side connection, refer to the Client SDK.

Lifecycle hooks

Agents have several lifecycle hooks that fire at different points:

HookWhen called
onStart(props?)Once when the agent first starts (before any connections)
onRequest(request)When an HTTP request is received (non-WebSocket)
onConnect(connection, ctx)When a new WebSocket connection is established
onMessage(connection, message)When a WebSocket message is received
onClose(connection, code, reason, wasClean)When a WebSocket connection closes
onError(connection, error)When a WebSocket error occurs

onStart

onStart() is called once when the agent first starts, before any connections are established:

JavaScript
export class MyAgent extends Agent {
async onStart() {
// Initialize resources
console.log(`Agent ${this.name} starting...`);
// Load data from storage
const savedData = this.sql`SELECT * FROM cache`;
for (const row of savedData) {
// Rebuild in-memory state from persistent storage
}
}
onConnect(connection) {
// By the time connections arrive, onStart has completed
}
}

Handling connections

Define onConnect and onMessage methods on your Agent to accept WebSocket connections:

JavaScript
import { Agent, Connection, ConnectionContext, WSMessage } from "agents";
export class ChatAgent extends Agent {
async onConnect(connection, ctx) {
// Connections are automatically accepted
// Access the original request for auth, headers, cookies
const url = new URL(ctx.request.url);
const token = url.searchParams.get("token");
if (!token) {
connection.close(4001, "Unauthorized");
return;
}
// Store user info on this connection
connection.setState({ authenticated: true });
}
async onMessage(connection, message) {
if (typeof message === "string") {
// Handle text message
const data = JSON.parse(message);
connection.send(JSON.stringify({ received: data }));
}
}
}

Connection object

Each connected client has a unique Connection object:

Property/MethodTypeDescription
idstringUnique identifier for this connection
stateStatePer-connection state object
setState(state)voidUpdate connection state
send(message)voidSend message to this client
close(code?, reason?)voidClose the connection

Per-connection state

Store data specific to each connection (user info, preferences, etc.):

JavaScript
export class ChatAgent extends Agent {
async onConnect(connection, ctx) {
const userId = new URL(ctx.request.url).searchParams.get("userId");
connection.setState({
userId: userId || "anonymous",
role: "user",
joinedAt: Date.now(),
});
}
async onMessage(connection, message) {
// Access connection-specific state
console.log(`Message from ${connection.state.userId}`);
}
}

Broadcasting to all clients

Use this.broadcast() to send a message to all connected clients:

JavaScript
export class ChatAgent extends Agent {
async onMessage(connection, message) {
// Broadcast to all connected clients
this.broadcast(
JSON.stringify({
from: connection.id,
message: message,
timestamp: Date.now(),
}),
);
}
// Broadcast from any method
async notifyAll(event, data) {
this.broadcast(JSON.stringify({ event, data }));
}
}

Excluding connections

Pass an array of connection IDs to exclude from the broadcast:

JavaScript
// Broadcast to everyone except the sender
this.broadcast(
JSON.stringify({ type: "user-typing", userId: "123" }),
[connection.id], // Do not send to the originator
);

Connection tags

Tag connections for easy filtering. Override getConnectionTags() to assign tags when a connection is established:

JavaScript
export class ChatAgent extends Agent {
getConnectionTags(connection, ctx) {
const url = new URL(ctx.request.url);
const role = url.searchParams.get("role");
const tags = [];
if (role === "admin") tags.push("admin");
if (role === "moderator") tags.push("moderator");
return tags; // Up to 9 tags, max 256 chars each
}
// Later, broadcast only to admins
notifyAdmins(message) {
for (const conn of this.getConnections("admin")) {
conn.send(message);
}
}
}

Connection management methods

MethodSignatureDescription
getConnections(tag?: string) => Iterable<Connection>Get all connections, optionally by tag
getConnection(id: string) => Connection | undefinedGet connection by ID
getConnectionTags(connection, ctx) => string[]Override to tag connections
broadcast(message, without?: string[]) => voidSend to all connections

Handling binary data

Messages can be strings or binary (ArrayBuffer / ArrayBufferView):

JavaScript
export class FileAgent extends Agent {
async onMessage(connection, message) {
if (message instanceof ArrayBuffer) {
// Handle binary upload
const bytes = new Uint8Array(message);
await this.processFile(bytes);
connection.send(
JSON.stringify({ status: "received", size: bytes.length }),
);
} else if (typeof message === "string") {
// Handle text command
const command = JSON.parse(message);
// ...
}
}
}

Error and close handling

Handle connection errors and disconnections:

JavaScript
export class ChatAgent extends Agent {
async onError(connection, error) {
console.error(`Connection ${connection.id} error:`, error);
// Clean up any resources for this connection
}
async onClose(connection, code, reason, wasClean) {
console.log(`Connection ${connection.id} closed: ${code} ${reason}`);
// Notify other clients
this.broadcast(
JSON.stringify({
event: "user-left",
userId: connection.state?.userId,
}),
);
}
}

Message types

TypeDescription
stringText message (typically JSON)
ArrayBufferBinary data
ArrayBufferViewTyped array view of binary data

Hibernation

Agents support hibernation — they can sleep when inactive and wake when needed. This saves resources while maintaining WebSocket connections.

Enabling hibernation

Hibernation is enabled by default. To disable:

JavaScript
export class AlwaysOnAgent extends Agent {
static options = { hibernate: false };
}

How hibernation works

  1. Agent is active, handling connections
  2. After a period of inactivity with no messages, the agent hibernates (sleeps)
  3. WebSocket connections remain open (handled by Cloudflare)
  4. When a message arrives, the agent wakes up
  5. onMessage is called as normal

What persists across hibernation

PersistsDoes not persist
this.state (agent state)In-memory variables
connection.stateTimers/intervals
SQLite data (this.sql)Promises in flight
Connection metadataLocal caches

Store important data in this.state or SQLite, not in class properties:

JavaScript
export class MyAgent extends Agent {
initialState = { counter: 0 };
// Do not do this - lost on hibernation
localCounter = 0;
onMessage(connection, message) {
// Persists across hibernation
this.setState({ counter: this.state.counter + 1 });
// Lost after hibernation
this.localCounter++;
}
}

Common patterns

Presence tracking

Track who is online using per-connection state. Connection state is automatically cleaned up when users disconnect:

JavaScript
export class PresenceAgent extends Agent {
onConnect(connection, ctx) {
const url = new URL(ctx.request.url);
const name = url.searchParams.get("name") || "Anonymous";
connection.setState({
name,
joinedAt: Date.now(),
lastSeen: Date.now(),
});
// Send current presence to new user
connection.send(
JSON.stringify({
type: "presence",
users: this.getPresence(),
}),
);
// Notify others that someone joined
this.broadcastPresence();
}
onClose(connection) {
// No manual cleanup needed - connection state is automatically gone
this.broadcastPresence();
}
onMessage(connection, message) {
if (message === "ping") {
connection.setState((prev) => ({
...prev,
lastSeen: Date.now(),
}));
connection.send("pong");
}
}
getPresence() {
const users = {};
for (const conn of this.getConnections()) {
if (conn.state) {
users[conn.id] = {
name: conn.state.name,
lastSeen: conn.state.lastSeen,
};
}
}
return users;
}
broadcastPresence() {
this.broadcast(
JSON.stringify({
type: "presence",
users: this.getPresence(),
}),
);
}
}

Chat room with broadcast

JavaScript
export class ChatRoom extends Agent {
onConnect(connection, ctx) {
const url = new URL(ctx.request.url);
const username = url.searchParams.get("username") || "Anonymous";
connection.setState({ username });
// Notify others
this.broadcast(
JSON.stringify({
type: "join",
user: username,
timestamp: Date.now(),
}),
[connection.id], // Do not send to the joining user
);
}
onMessage(connection, message) {
if (typeof message !== "string") return;
const { username } = connection.state;
this.broadcast(
JSON.stringify({
type: "message",
user: username,
text: message,
timestamp: Date.now(),
}),
);
}
onClose(connection) {
const { username } = connection.state || {};
if (username) {
this.broadcast(
JSON.stringify({
type: "leave",
user: username,
timestamp: Date.now(),
}),
);
}
}
}

Connecting from clients

For browser connections, use the Agents client SDK:

  • Vanilla JS: AgentClient from agents/client
  • React: useAgent hook from agents/react

Refer to Client SDK for full documentation.

Next steps