Pass forwardedProps to useAppChat(). Every request from that chat carries the value, and the model does not see it.
Put forwardedProps on the screen, not on chatOptions, because the value changes for each user:
import { fetchServerSentEvents } from '@tanstack/ai-react'
import { createChatHook } from '@tanstack/ai-react/ui'
const chatOptions = {
connection: fetchServerSentEvents('/api/chat'),
}
const { useAppChat, useChatContext } = createChatHook({
options: chatOptions,
components: {
input: () => {
const chat = useChatContext()
return (
<button onClick={() => void chat.sendMessage('What are my invoices?')}>
Ask
</button>
)
},
layout: ({ Messages, Input }) => (
<main>
<Messages />
<Input />
</main>
),
message: ({ Parts }) => <article><Parts /></article>,
},
partsComponents: {
text: ({ part }) => <p>{part.content}</p>,
fallback: () => null,
},
})
export function TenantChat({ tenantId }: { tenantId: string }) {
const chat = useAppChat({
threadId: `tenant-${tenantId}`,
forwardedProps: { tenantId },
})
return <chat.AppChat />
}Read the value with chatParamsFromRequest. Then put it in context, where tools and middleware read it:
import {
chat,
chatParamsFromRequest,
toServerSentEventsResponse,
} from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
export async function POST(request: Request) {
const params = await chatParamsFromRequest(request)
const tenantId =
typeof params.forwardedProps.tenantId === 'string'
? params.forwardedProps.tenantId
: undefined
if (!tenantId) return new Response('Missing tenant', { status: 400 })
const stream = chat({
adapter: openaiText('gpt-6.1-sol'),
messages: params.messages,
threadId: params.threadId,
context: { tenantId },
})
return toServerSentEventsResponse(stream)
}Click Ask. The route gets tenantId from the request, and the model never sees it.
forwardedProps comes from the browser, so a user can change it. Do not trust it for identity or authorization:
The example above makes sure that tenantId is a string, and rejects the request without it. A real app also makes sure that the signed-in user can use that tenant.
If a screen can show more than one chat, give each instance its own threadId. Two instances with different thread ids keep separate histories.