---
title: State Adapters
description: State adapters store thread subscriptions, distributed locks, and cached data for your bot.
docs_index: /llms.txt
lastUpdated: 2026-10-22
type: overview
prerequisites:
  - /docs/getting-started
related:
  - /docs/concurrency
  - /docs/history
  - /docs/testing
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

State adapters store thread subscriptions, distributed locks, and cached data. Every `Chat` instance needs one. Browse the available state adapters on the [Adapters](/adapters) page.

## What state adapters manage

### Thread subscriptions

When your bot calls `thread.subscribe()`, the state adapter persists that subscription. On later webhooks, the SDK checks it to route messages to `onSubscribedMessage`. With a production adapter such as Redis or PostgreSQL, subscriptions survive restarts and are shared across instances.

### Distributed locking

When a message arrives, the SDK acquires a lock on its thread, or on its channel for adapters that lock per channel, such as WhatsApp and Telegram. The lock stops two handlers from processing messages in the same thread at once, even when your bot runs on several serverless instances.

The `concurrency` option decides what happens when the lock is already held. By default, the new message is dropped with a `LockError`. You can also queue it, debounce a burst of messages, or skip locking entirely. See [Concurrency](/docs/concurrency) for the strategies and for [lock scope](/docs/concurrency#lock-scope).

The deprecated `onLockConflict` option still works under the default `drop` strategy. Set it to `'force'` to release the held lock and let the new message through, for example so a new message can interrupt a long-running AI response:

```typescript
const chat = new Chat({
  userName: 'my-bot',
  adapters: { slack },
  state: createRedisState(),
  onLockConflict: 'force',
});
```

You can also pass a callback that decides per message:

```typescript
onLockConflict: (threadId, message) => {
  return message.text.includes('stop') ? 'force' : 'drop';
}
```

Force-releasing a lock doesn't cancel the previous handler. It keeps running without the lock, so two handlers can briefly run on the same thread at the same time.

### Caching and storage

State adapters also provide key-value storage with a TTL. The SDK uses it for thread and channel state (`thread.setState()`), for message deduplication, which ignores webhooks a platform delivers more than once, and for internal caches. Lists back the [History API](/docs/history), and queues back the `queue`, `debounce`, and `burst` concurrency strategies.

To build your own state adapter, implement the `StateAdapter` interface exported from `chat`. The `@chat-adapter/state-memory` package is a small implementation you can use as a reference.

## Production state on Vercel

The [Memory](/adapters/official/memory) adapter keeps state in process memory, so it suits local development and tests. In production, use [Redis](/adapters/official/redis), [ioredis](/adapters/official/ioredis), or [PostgreSQL](/adapters/official/postgres) so subscriptions, locks, and cached data are shared across instances and survive restarts.

If you deploy to Vercel, you can provision either database from the [Vercel Marketplace](https://vercel.com/marketplace/category/storage) with the Vercel CLI:

| Provider        | Command                                     | State adapter                                                                                 |
| --------------- | ------------------------------------------- | --------------------------------------------------------------------------------------------- |
| Upstash Redis   | `vercel integration add upstash/upstash-kv` | [Redis](/adapters/official/redis#vercel-marketplace) or [ioredis](/adapters/official/ioredis) |
| Redis Cloud     | `vercel integration add redis`              | [Redis](/adapters/official/redis#vercel-marketplace) or [ioredis](/adapters/official/ioredis) |
| Neon            | `vercel integration add neon`               | [PostgreSQL](/adapters/official/postgres#vercel-marketplace)                                  |
| Supabase        | `vercel integration add supabase`           | [PostgreSQL](/adapters/official/postgres#vercel-marketplace)                                  |
| Prisma Postgres | `vercel integration add prisma`             | [PostgreSQL](/adapters/official/postgres#vercel-marketplace)                                  |
| Nile            | `vercel integration add nile`               | [PostgreSQL](/adapters/official/postgres#vercel-marketplace)                                  |

Run the command from a linked project directory:

```bash
vercel link
vercel integration add upstash/upstash-kv
```

`vercel integration add` creates the database, connects it to the linked project, adds its credentials as environment variables, and pulls them into your local env file. Each adapter page lists the environment variables the adapter reads and how to pass a different one.

#### [List a vendor-official adapter](/docs/contributing/vendor-official)

How platform vendors qualify and list a maintained adapter in the Vendor Official tier.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)