State Adapters
State adapters store thread subscriptions, distributed locks, and cached data. Every Chat instance needs one. Browse the available state adapters on the 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 for the strategies and for 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:
You can also pass a callback that decides per message:
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, 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 adapter keeps state in process memory, so it suits local development and tests. In production, use Redis, ioredis, or PostgreSQL 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 with the Vercel CLI:
| Provider | Command | State adapter |
|---|---|---|
| Upstash Redis | vercel integration add upstash/upstash-kv | Redis or ioredis |
| Redis Cloud | vercel integration add redis | Redis or ioredis |
| Neon | vercel integration add neon | PostgreSQL |
| Supabase | vercel integration add supabase | PostgreSQL |
| Prisma Postgres | vercel integration add prisma | PostgreSQL |
| Nile | vercel integration add nile | PostgreSQL |
Run the command from a linked project directory:
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.
Read more
Overlapping Messages
Control how overlapping messages on the same thread are handled, with the burst, queue, debounce, drop, and concurrent strategies.
History
Store and retrieve message history across user, thread, and channel scopes.
Testing
Test your bot handlers and custom adapters with @chat-adapter/tests, which provides Vitest factories, custom matchers, and a setup file.
Getting Started
Pick a guide to start building with Chat SDK.