---
title: MySQL
description: Community MySQL state adapter for Chat SDK built on mysql2. Persistence, distributed locking, caching, lists, and queues without a separate Redis dependency.
tagline: Community MySQL state adapter for Chat SDK. Use when MySQL is your primary datastore and you want state persistence without standing up a separate Redis cluster.
package: chat-state-mysql
---

# MySQL



## Install

<PackageInstall package="chat chat-state-mysql" />

## Quick start

`createMySqlState()` auto-detects `MYSQL_URL` (or `DATABASE_URL`), so you can call it with no arguments:

```typescript title="lib/bot.ts" lineNumbers
import { Chat } from "chat";
import { createMySqlState } from "chat-state-mysql";

const bot = new Chat({
  userName: "mybot",
  adapters: {
    /* ... */
  },
  state: createMySqlState(),
});
```

To provide a URL explicitly:

```typescript
const state = createMySqlState({
  url: "mysql://root:root@localhost:3306/chat",
});
```

### Using an existing client

```typescript
import mysql from "mysql2/promise";

const client = mysql.createPool(process.env.MYSQL_URL);
const state = createMySqlState({ client });
```

## Configuration

<TypeTable
  type={{
  url: {
    type: "string",
    description:
      "MySQL connection URL. Auto-detected from `MYSQL_URL` or `DATABASE_URL` when omitted.",
  },
  client: {
    type: "mysql.Pool",
    description:
      "Existing `mysql2/promise` Pool instance to reuse instead of creating a new one.",
  },
  keyPrefix: {
    type: "string",
    default: '"chat-sdk"',
    description: "Prefix for all state rows.",
  },
  logger: {
    type: "Logger",
    default: 'ConsoleLogger("info").child("mysql")',
    description: "Logger instance.",
  },
}}
/>

Either `url`, the `MYSQL_URL` / `DATABASE_URL` env var, or `client` is required.

## Data model

The adapter creates these tables automatically on `connect()`:

```sql
chat_state_subscriptions
chat_state_locks
chat_state_cache
chat_state_lists
chat_state_queues
```

All rows are namespaced by `key_prefix`. Prefixes, thread IDs, and cache keys are stored as text, with SHA-256 hash columns used for MySQL primary keys and indexes — so long platform IDs and multibyte prefixes remain supported.

The schema avoids window functions and generated columns, and is compatible with MySQL 5.7 and newer.

## Locking considerations

The Redis state adapters use atomic `SET NX PX` for lock acquisition. The MySQL adapter uses InnoDB row-level locking through `INSERT ... ON DUPLICATE KEY UPDATE`, replacing a lock only when the stored `expires_at` timestamp has passed. That's safe for typical multi-instance workloads, but Redis remains a better fit for high-contention distributed locking.

## Expired row cleanup

Unlike Redis, MySQL does not automatically delete expired rows. The adapter performs opportunistic cleanup:

* Expired locks are overwritten on the next `acquireLock()` call.
* Expired cache entries are deleted on the next `get()` call for that key.
* Expired queue entries are purged during queue operations.

For high-throughput deployments, run a periodic cleanup job:

```sql
DELETE FROM chat_state_locks WHERE expires_at <= CURRENT_TIMESTAMP(3);
DELETE FROM chat_state_cache WHERE expires_at <= CURRENT_TIMESTAMP(3);
DELETE FROM chat_state_lists WHERE expires_at <= CURRENT_TIMESTAMP(3);
DELETE FROM chat_state_queues WHERE expires_at <= CURRENT_TIMESTAMP(3);
```

## Feature support

<FeatureSupport />
