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:

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

You can also pass a callback that decides per message:

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, 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:

ProviderCommandState adapter
Upstash Redisvercel integration add upstash/upstash-kvRedis or ioredis
Redis Cloudvercel integration add redisRedis or ioredis
Neonvercel integration add neonPostgreSQL
Supabasevercel integration add supabasePostgreSQL
Prisma Postgresvercel integration add prismaPostgreSQL
Nilevercel integration add nilePostgreSQL

Run the command from a linked project directory:

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.

Read more