---
title: "createChatHandler"
description: "The server router, its required runtime dependencies, hooks, limits, and cross-origin options."
---

`createChatHandler(options)` returns `GET`, `POST`, `DELETE`, and `OPTIONS` for a
single catch-all route. It authenticates requests, binds persistence and storage
to the verified user, streams the model response, and persists completed turns.

```ts
export const { GET, POST, DELETE, OPTIONS } = createChatHandler({
  getUserId: getChatUserId,
  model: anthropic('claude-sonnet-4-5'),
  store: createDrizzleChatStore(),
});
```

## Required identity boundary

- **`getUserId`** · `(request: Request) => string \| null \| Promise<string \| null>` · required

  Return identity from a verified server session. `null` produces `401`. Never use
  request-body, query-string, or browser-controlled header identity.

## Conditionally required runtime dependencies


- **`model`** · `LanguageModel \| ((ctx) => LanguageModel \| Promise<LanguageModel>)`

  Required unless `getHostedConfig` returns a model. Precedence is code, then hosted
  config, then an error. There is no package-default model.

- **`store`** · `ChatStoreFactory`

  Currently required. Pass a Drizzle, hosted, or custom factory. Omitting it throws
  on the first chat request; there is no silent persistence default.

- **`storage`** · `StorageAdapterFactory`

  Optional. Omitting it disables uploads.

- **`buildTools`** · `(ctx) => BuiltTools \| Promise<BuiltTools>`

  Optional request-scoped tools. Return `{ tools, cleanup? }`; cleanup runs once
  when the request settles.


## Configuration and hooks


- **`getHostedConfig`** · `(ctx) => HostedAgentConfig \| null \| Promise<HostedAgentConfig \| null>`

  Best-effort hosted configuration. Code values win. The current hosted shape can
  supply model, system prompt, greeting, appearance, output cap, and follow-up
  settings.

- **`buildSystemPrompt`** · `(ctx) => string \| Promise<string>`

  Server-authoritative system prompt.

- **`transformMessages`** · `(messages, ctx) => ModelMessage[] \| Promise<ModelMessage[]>`

  Transform the pruned conversation payload.

- **`getContext`** · `(ctx, clientContext) => Record<string, unknown> \| null \| Promise<...>`

  Validate or replace untrusted client context.

- **`trustClientContext`** · `boolean` · default `false`

  Inject client context verbatim. Use only for non-sensitive hints.

- **`followUps`** · `boolean \| ServerFollowUpConfig`

  Generate server-side follow-up chips. Default off.

- **`onFeedback`** · `(feedback, ctx) => void \| Promise<void>`

  Persist verified feedback submissions.

- **`onChatFinish`** · `({ ctx, messages, usage, providerMetadata }) => void \| Promise<void>`

  Post-persistence telemetry hook.

- **`onError`** · `(error) => string`

  Map stream errors to safe user-facing text.

- **`logErrors`** · `boolean` · default `true`

  Keep server-side stream error logging enabled unless another sink replaces it.

- **`stopWhen`** · `StopCondition<ToolSet>`

  Bound tool-call loops.

- **`retrieval`** · `RetrievalConfig`

  Optional read-only RAG integration.

- **`memory`** · `MemoryConfig`

  Optional long-term memory.

- **`summarizeHistory`** · `HistorySummarizer`

  Optional summary of messages dropped by the sliding window.


## Limits


- **`maxOutputTokens`** · `number`

  Code cap, then hosted cap, then provider default.

- **`maxHistoryMessages`** · `number` · default `30`

  Recent messages sent to the model.

- **`maxMessageChars`** · `number` · default `4000`

  Per-message character cap; `0` disables it.

- **`maxRequestBytes`** · `number` · default `1 MiB`

  Hard cap on actual request-body bytes.

- **`streamTimeoutMs`** · `number`

  Optional wall-clock stream timeout; off by default.

- **`upload.allowedMediaTypes`** · `string[]`

  Exact server MIME allow-list.

- **`upload.maxBytes`** · `number` · default `5 MiB`

  Per-file server limit.


## Cross-origin embeds

Same-origin applications need no CORS configuration. A widget calling a handler
on another origin needs an explicit `cors` policy, and credentialed requests
also need `requestCredentials` set on the client.

```ts
export const { GET, POST, DELETE, OPTIONS } = createChatHandler({
  getUserId: getChatUserId,
  model,
  store,
  cors: {
    allowOrigins: ['https://docs.example.com'],
    allowCredentials: true,
  },
});
```

```tsx
<ChatWidget
  apiBase="https://app.example.com/api/chat"
  requestCredentials="include"
/>
```

Use credentials only when the verified session actually depends on a
cross-origin cookie. Never reflect arbitrary origins with credentials.

## BuiltTools

```ts
interface BuiltTools {
  tools: ToolSet;
  cleanup?: () => void | Promise<void>;
}
```

Cleanup runs exactly once on success, stream error, or abort. Use it for MCP
clients and other request-scoped resources.
