---
title: Creating a Chat Instance
description: Initialize the Chat class with adapters, state, and configuration options.
type: guide
prerequisites:
  - /docs/getting-started
related:
  - /docs/handling-events
  - /docs/adapters
  - /docs/state-adapters
  - /docs/api/chat
  - /docs/error-handling
---

# Creating a Chat Instance



The `Chat` class is the main entry point for your bot. It coordinates adapters, routes events to your handlers, and manages thread state.

## Basic setup

```typescript title="lib/bot.ts" lineNumbers
import { Chat } from "chat";
import { createSlackAdapter } from "@chat-adapter/slack";
import { createRedisState } from "@chat-adapter/state-redis";

const bot = new Chat({
  userName: "mybot",
  adapters: {
    slack: createSlackAdapter(),
  },
  state: createRedisState(),
});

bot.onNewMention(async (thread) => {
  await thread.subscribe();
  await thread.post("Hello! I'm listening to this thread.");
});
```

<Callout type="info">
  This example uses Redis. Chat SDK also supports [PostgreSQL](/adapters/official/postgres) and [ioredis](/adapters/official/ioredis) as production state adapters. See [State Adapters](/docs/state-adapters) for all options.
</Callout>

Adapter and state factories read their credentials from environment variables such as `SLACK_BOT_TOKEN`, `SLACK_SIGNING_SECRET`, and `REDIS_URL`, so the example above passes no options. Pass explicit values to override them. For setup UIs and build scripts, the [`chat/adapters` catalog](/docs/adapters#adapter-catalog-chatadapters) lists official and vendor-official adapter env specs without importing adapter packages.

## Multiple adapters

Register several [adapters](/adapters) to run the same bot on each of those platforms:

```typescript title="lib/bot.ts" lineNumbers
import { Chat } from "chat";
import { createSlackAdapter } from "@chat-adapter/slack";
import { createTeamsAdapter } from "@chat-adapter/teams";
import { createDiscordAdapter } from "@chat-adapter/discord";
import { createRedisState } from "@chat-adapter/state-redis";

const bot = new Chat({
  userName: "mybot",
  adapters: {
    slack: createSlackAdapter(),
    teams: createTeamsAdapter(),
    discord: createDiscordAdapter(),
  },
  state: createRedisState(),
});
```

Your event handlers work the same way for every registered adapter, because the SDK normalizes messages, threads, and reactions into one format. Where platforms differ, such as rate limits or unsupported features, the SDK throws typed errors. See [Error Handling](/docs/error-handling).

## Configuration options

| Option                             | Type                                                                              | Default         | Description                                                                                                                                                             |
| ---------------------------------- | --------------------------------------------------------------------------------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `userName`                         | `string`                                                                          | *required*      | Default bot username across all adapters                                                                                                                                |
| `adapters`                         | `Record<string, Adapter>`                                                         | *required*      | Map of adapter name to adapter instance                                                                                                                                 |
| `state`                            | `StateAdapter`                                                                    | *required*      | State adapter for subscriptions and locking                                                                                                                             |
| `logger`                           | `Logger \| LogLevel`                                                              | `"info"`        | Logger instance or log level (`"debug"`, `"info"`, `"warn"`, `"error"`, `"silent"`)                                                                                     |
| `dedupeTtlMs`                      | `number`                                                                          | `600000`        | TTL in ms for message deduplication (10 minutes)                                                                                                                        |
| `concurrency`                      | `"drop" \| "queue" \| "debounce" \| "burst" \| "concurrent" \| ConcurrencyConfig` | `"drop"`        | Strategy for overlapping messages on the same thread                                                                                                                    |
| `streamingUpdateIntervalMs`        | `number`                                                                          | `500`           | Update interval in ms for post+edit streaming                                                                                                                           |
| `fallbackStreamingPlaceholderText` | `string \| null`                                                                  | `"..."`         | Placeholder text while streaming starts. Set to `null` to skip                                                                                                          |
| `lockScope`                        | `"thread" \| "channel" \| function`                                               | Adapter default | Whether messages share a lock per thread or per channel. See [Lock scope](/docs/concurrency#lock-scope)                                                                 |
| `history`                          | `HistoryConfig`                                                                   |                 | User, thread, and channel history settings. See [History](/docs/history)                                                                                                |
| `onLockConflict`                   | `'drop' \| 'force' \| (threadId, message) => 'drop' \| 'force'`                   | `"drop"`        | Deprecated: use `concurrency`. Under the `drop` strategy, `'force'` releases the existing lock and re-acquires it so a new message can interrupt a long-running handler |

## Accessing adapters

Use `getAdapter` to access platform-specific APIs when you need functionality beyond the unified interface:

```typescript title="lib/bot.ts" lineNumbers
import type { SlackAdapter } from "@chat-adapter/slack";

const slack = bot.getAdapter("slack") as SlackAdapter;
await slack.setSuggestedPrompts(channelId, threadTs, [
  { title: "Get started", message: "What can you help me with?" },
]);
```

For typed access to the platform's native API client, use the SDK-named getter on each adapter:

```typescript title="lib/bot.ts" lineNumbers
const slack = bot.getAdapter("slack").webClient; // WebClient
const linear = bot.getAdapter("linear").linearClient; // LinearClient
const github = bot.getAdapter("github").octokit; // Octokit
```

The previous `.client` getter still works as a deprecated alias on all three adapters.

See [`getAdapter`](/docs/api/chat#getadapter) for multi-tenant constraints.

## Webhook routing

The `webhooks` property provides a typed handler for each registered adapter. Wire each one to a route in your HTTP framework:

```typescript title="app/api/webhooks/slack/route.ts" lineNumbers
import { after } from "next/server";
import { bot } from "@/lib/bot";

export async function POST(request: Request): Promise<Response> {
  return bot.webhooks.slack(request, {
    waitUntil: (task) => after(() => task),
  });
}
```

```typescript title="app/api/webhooks/teams/route.ts" lineNumbers
import { after } from "next/server";
import { bot } from "@/lib/bot";

export async function POST(request: Request): Promise<Response> {
  return bot.webhooks.teams(request, {
    waitUntil: (task) => after(() => task),
  });
}
```

Your event handlers can keep running after the webhook responds. `waitUntil` keeps the function alive until they finish, so a serverless platform doesn't stop them early. These examples use Next.js `after`. In other Vercel Functions, pass `waitUntil` from `@vercel/functions`.

## Lifecycle

The Chat instance initializes lazily on the first webhook. You can also initialize manually:

```typescript title="lib/bot.ts" lineNumbers
await bot.initialize();
```

For graceful shutdown, such as during serverless teardown, call `shutdown`:

```typescript title="lib/bot.ts" lineNumbers
await bot.shutdown();
```

## Singleton pattern

Register a singleton when you need to access the Chat instance from multiple files:

```typescript title="lib/bot.ts" lineNumbers
const bot = new Chat({ /* ...config */ }).registerSingleton();
export default bot;
```

```typescript title="lib/utils.ts" lineNumbers
import { Chat } from "chat";

const bot = Chat.getSingleton();
```

## Direct messaging

Open a DM thread with a user by passing their platform user ID or an `Author` object:

```typescript title="lib/bot.ts" lineNumbers
const dm = await bot.openDM("U123ABC");
await dm.post("Hey! Just wanted to follow up on your request.");
```

## Channel access

Get a channel directly by its ID:

```typescript title="lib/bot.ts" lineNumbers
const channel = bot.channel("slack:C123ABC");
await channel.post("Announcement: deploy complete!");
```


---

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)