> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/cloudflare/agents/llms.txt
> Use this file to discover all available pages before exploring further.

# State Management

> Manage Agent state with automatic persistence and client synchronization

## Overview

Agents provide built-in state management with automatic persistence to Durable Object storage and real-time synchronization to all connected WebSocket clients.

## Setting State

### setState()

Update the Agent's state. Persists to storage and broadcasts to all connected clients.

<ParamField path="state" type="State" required>
  The new state to set
</ParamField>

```typescript theme={null}
class CounterAgent extends Agent<Env, { count: number }> {
  initialState = { count: 0 };

  @callable()
  async increment() {
    this.setState({ count: this.state.count + 1 });
  }
}
```

**Throws:** `Error` if called from a readonly connection context

### How setState() Works

1. **Validation** - Calls `validateStateChange()` hook (synchronous)
2. **Persistence** - Saves state to Durable Object storage
3. **Broadcast** - Sends state update to all connected clients
4. **Notification** - Calls `onStateChanged()` hook (async, non-blocking)

## Reading State

### state

Access the current state via the `state` property.

```typescript theme={null}
@callable()
async getCount() {
  return this.state.count;
}
```

### initialState

Define the initial state for new Agent instances.

```typescript theme={null}
class TodoAgent extends Agent<Env, { todos: Todo[] }> {
  initialState = { todos: [] };
}
```

<Note>
  `initialState` is only used when the Agent is first created. If state already exists in storage, it takes precedence.
</Note>

## State Lifecycle Hooks

### validateStateChange()

Called **before** state is persisted. Throw an error to reject the update. Must be synchronous.

<ParamField path="nextState" type="State" required>
  The proposed new state
</ParamField>

<ParamField path="source" type="Connection | 'server'" required>
  Source of the state update
</ParamField>

```typescript theme={null}
validateStateChange(nextState: State, source: Connection | "server") {
  // Only allow server to set admin fields
  if (source !== "server" && nextState.isAdmin) {
    throw new Error("Only server can set isAdmin");
  }

  // Validate state shape
  if (nextState.count < 0) {
    throw new Error("Count cannot be negative");
  }
}
```

<Warning>
  `validateStateChange()` must be synchronous. Use `onStateChanged()` for async operations.
</Warning>

### onStateChanged()

Called **after** state has been persisted and broadcast. This is a notification hook—errors are routed to `onError()` and do not affect persistence.

<ParamField path="state" type="State | undefined" required>
  The new state
</ParamField>

<ParamField path="source" type="Connection | 'server'" required>
  Source of the state update
</ParamField>

```typescript theme={null}
async onStateChanged(state: State, source: Connection | "server") {
  // Log state changes
  console.log("State updated:", state);

  // Trigger side effects
  if (state.count >= 100) {
    await this.queue("celebrate");
  }

  // Update external services
  await fetch("https://api.example.com/notify", {
    method: "POST",
    body: JSON.stringify({ state })
  });
}
```

## Client-Side State Management

### useAgent Hook

React hook for real-time state synchronization.

```typescript theme={null}
import { useAgent } from "agents/react";

function Counter() {
  const [state, setState] = useState<{ count: number }>();

  const agent = useAgent<{ count: number }>({
    agent: "CounterAgent",
    name: "default",
    onStateUpdate: (newState) => setState(newState)
  });

  return (
    <div>
      <p>Count: {state?.count ?? 0}</p>
      <button onClick={() => agent.call("increment")}>
        Increment
      </button>
    </div>
  );
}
```

See [useAgent hook](/api/use-agent-hook) for full documentation.

### AgentClient

Vanilla JavaScript client for state synchronization.

```typescript theme={null}
import { AgentClient } from "agents/client";

const client = new AgentClient<{ count: number }>({
  host: "localhost:1999",
  agent: "CounterAgent",
  name: "default",
  onStateUpdate: (state) => {
    console.log("State updated:", state);
  }
});

// Update state from client
client.setState({ count: 42 });
```

See [AgentClient](/api/agent-client) for full documentation.

## Connection-Level State

### Readonly Connections

Mark connections as readonly to prevent state updates.

```typescript theme={null}
class SecureAgent extends Agent<Env, State> {
  shouldConnectionBeReadonly(connection: Connection, ctx: ConnectionContext) {
    const url = new URL(ctx.request.url);
    const isViewer = url.searchParams.get("role") === "viewer";
    return isViewer;
  }
}
```

### connection.setState()

Connections can have their own isolated state (separate from Agent state).

```typescript theme={null}
async onConnect(connection: Connection, ctx: ConnectionContext) {
  const userId = new URL(ctx.request.url).searchParams.get("user");
  connection.setState({ userId, connectedAt: Date.now() });
}
```

**Access connection state:**

```typescript theme={null}
const { userId } = connection.state as { userId: string };
```

## State Persistence

State is automatically persisted to Durable Object SQL storage:

```sql theme={null}
CREATE TABLE cf_agents_state (
  id TEXT PRIMARY KEY NOT NULL,
  state TEXT
);
```

You can access the raw storage if needed:

```typescript theme={null}
const rows = this.sql<{ state: string }>`
  SELECT state FROM cf_agents_state WHERE id = 'cf_state_row_id'
`;
```

<Warning>
  Direct SQL access to state storage is not recommended. Use `setState()` and `state` instead.
</Warning>

## State Broadcasting

When state changes, all connected clients receive an update message:

```json theme={null}
{
  "type": "cf_agent_state",
  "state": { "count": 42 }
}
```

You can suppress protocol messages for specific connections:

```typescript theme={null}
shouldSendProtocolMessages(connection: Connection, ctx: ConnectionContext) {
  // Disable protocol messages for binary-only clients (e.g., MQTT)
  const contentType = ctx.request.headers.get("content-type");
  return contentType !== "application/octet-stream";
}
```

## Best Practices

### Atomic Updates

Always pass the complete state object to `setState()`:

```typescript theme={null}
// ✅ Good
this.setState({ ...this.state, count: this.state.count + 1 });

// ❌ Bad - state is replaced entirely
this.setState({ count: this.state.count + 1 });
```

### Validate Before Persist

Use `validateStateChange()` to enforce invariants:

```typescript theme={null}
validateStateChange(nextState: State) {
  if (nextState.balance < 0) {
    throw new Error("Balance cannot be negative");
  }
}
```

### Async Side Effects

Use `onStateChanged()` for async operations:

```typescript theme={null}
async onStateChanged(state: State) {
  // Don't block state updates with slow operations
  await this.env.KV.put("latest-state", JSON.stringify(state));
}
```

### State Size

Keep state small (under 128KB recommended). For large data, use SQL or KV:

```typescript theme={null}
// ✅ Good - store ID in state, fetch full data as needed
this.setState({ currentDocumentId: "doc-123" });

// ❌ Bad - storing large document in state
this.setState({ currentDocument: { ...largeDocument } });
```

## Related

* [Agent Class](/api/agent-class) - Agent base class
* [useAgent Hook](/api/use-agent-hook) - React state synchronization
* [AgentClient](/api/agent-client) - Client-side state management
