createChatHandler
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.
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>requiredReturn identity from a verified server session. null produces 401. Never use
request-body, query-string, or browser-controlled header identity.
Conditionally required runtime dependencies
modelLanguageModel | ((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.
storeChatStoreFactoryCurrently required. Pass a Drizzle, hosted, or custom factory. Omitting it throws on the first chat request; there is no silent persistence default.
storageStorageAdapterFactoryOptional. 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>transformMessages(messages, ctx) => ModelMessage[] | Promise<ModelMessage[]>getContext(ctx, clientContext) => Record<string, unknown> | null | Promise<...>trustClientContextbooleandefault: falsefollowUpsboolean | ServerFollowUpConfigonFeedback(feedback, ctx) => void | Promise<void>onChatFinish({ ctx, messages, usage, providerMetadata }) => void | Promise<void>onError(error) => stringlogErrorsbooleandefault: truestopWhenStopCondition<ToolSet>retrievalRetrievalConfigmemoryMemoryConfigsummarizeHistoryHistorySummarizerLimits
maxOutputTokensnumbermaxHistoryMessagesnumberdefault: 30maxMessageCharsnumberdefault: 40000 disables it.maxRequestBytesnumberdefault: 1 MiBstreamTimeoutMsnumberupload.allowedMediaTypesstring[]upload.maxBytesnumberdefault: 5 MiBCross-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.
export const { GET, POST, DELETE, OPTIONS } = createChatHandler({
getUserId: getChatUserId,
model,
store,
cors: {
allowOrigins: ['https://docs.example.com'],
allowCredentials: true,
},
});<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
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.