BTST
PluginsQuickstartDocs
Live Blog

From research to product evaluation

Evaluating a publishing workflow for an app you already own?

See what the BTST Blog plugin adds to an existing React or Next.js app
BTST

Open-source TypeScript features for the React application, data, and deployment you already own.

Released plugins

  • Blog
  • AI Chat
  • CMS
  • Form Builder
  • UI Builder
  • Kanban
  • Comments
  • Media
  • Route Docs
  • OpenAPI
  • Better Auth UI

Resources

  • Quickstart
  • Documentation
  • All plugins
  • Live Blog
  • GitHub (opens in a new tab)
© 2026 BTST. Open source under the MIT License.
AI Chat
September 12, 2026ReactAI Chat

React AI Chat: Keep Conversation History Private

Choose public or persisted chat, connect server identity, and verify ownership across history, streaming, edits, and retries.

React AI Chat: Keep Conversation History Private

A streaming reply and a private conversation history solve different problems. Before adding chat to a React app, decide who may start a conversation, who may reopen it, and what happens when the same user edits or retries a message from another tab.

BTST AI Chat supplies the chat surface, streaming operations, and conversation models. Your application supplies verified identity, permission rules, a model provider, and operating limits. This guide describes the released @btst/stack@3.0.0 behavior.

Choose the persistence mode first#

The backend's access option defaults to "authorized". That mode supports persisted conversation and message history. Explicit "public" mode streams without storing conversation history, and history operations are unavailable. The client has a separate mode setting: "authenticated" or "public". Configure both deliberately; a client setting cannot change the backend's access policy.

This factory fragment selects the persisted mode while leaving model selection to the application:

TS
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
import type { LanguageModel } from "ai";
import { aiChatBackendPlugin } from "@btst/stack/plugins/ai-chat/api";

export function privateChatPlugin(model: LanguageModel) {
  return aiChatBackendPlugin({ model, access: "authorized" });
}

This is one plugin entry, not a complete authentication setup. Register it in a backend stack with server authorization and a persistent database adapter. Follow the AI Chat installation guide for the matching client plugin, provider, CSS, and framework routes.

Use server identity for ownership#

Resolve the session or token on the server and return the application's validated identity through createServerAuth. Define the relevant rules with aiChatPermissions, then supply that authorization configuration to the stack. A user ID sent in a browser payload is not proof that the caller owns that account.

Think about collection access separately from access to one conversation. In the released operations, history listing derives a user scope; individual conversation operations resolve stored ownership facts. Permission to open the history page should not become permission to read every conversation by its ID.

An owner-only product can use this policy table as a review checklist:

OperationIntended rule
List historyA verified user can list their scoped history
Create a conversationA verified user may start one
Read, rename, or deleteThe verified user owns the resolved conversation
Send to an existing conversationThe verified user owns it
Edit or retry a messageThe user owns its resolved conversation
Start a streamThe same access policy applies to the validated message intent

The plugin also declares separate attachment and tool-activation permissions. Review those when enabling files or tools. A tool that reads customer records still needs its own resource access checks; permission to chat does not authorize every possible database read.

The released permission definitions distinguish send, edit, retry, attachments, tools, and stream start. Avoid granting only the visible button's operation and assuming the other paths inherit it.

Configure transactions before testing history#

Authorized streaming and ownership-sensitive history writes require an adapter with real isolated transaction support. For supported Drizzle, Prisma, and Kysely adapters, enable transaction: true as documented by the plugin. Unsupported transactional configuration can fail with ATOMIC_TRANSACTION_REQUIRED.

The transaction matters when conversation state changes during an in-flight request. Released operations compare the state used for authorization with current state and can reject stale work. A successful network connection does not establish that a reply was committed to history. Test refresh and reopen after a completed response, and surface persistence or conflict errors in the UI.

Keep the server's identity partition consistent with the identity hydrated into the client. The plugin's identityPartition helps align protected query keys; it is not an authorization credential. On sign-out or account changes, verify that the previous user's history disappears rather than remaining visible from cached data.

Verify with two accounts#

Create a conversation as user A, finish a reply, then reload and reopen it. As user B, try its direct URL and the underlying history, update, delete, edit, and retry operations. For an owner-only policy, none should expose or change A's content. Repeat anonymously.

Then edit a conversation from two tabs and exercise the failure path. Check that rejected work does not appear as a successfully saved exchange. Keep prompts, attachments, and conversation text out of routine analytics; fixed event names and coarse success states usually answer adoption questions without storing the conversation again.

Use the AI Chat plugin page to evaluate the supplied UI and the installation documentation to connect it to your framework. For request transport, see streaming API routes in TanStack Start.

In This Post

Choose the persistence mode firstUse server identity for ownershipConfigure transactions before testing historyVerify with two accounts