Pass forwardedProps to useAppChat(). Every request from that instance carries it.
const chat = useAppChat({
forwardedProps: { tenantId: 'tenant_456' },
})The value never enters the prompt. The model does not see it.
forwardedProps belongs on the screen, not on chatOptions, because it changes per 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 it with chatParamsFromRequest, then map it into context. Tools and middleware read context.
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-5.6'),
messages: params.messages,
context: { tenantId },
})
return toServerSentEventsResponse(stream)
}forwardedProps comes from the browser. A user can change it.
Never trust it for identity or authorization. Get the user from your session or token on the server, and use forwardedProps only for values that are safe to be wrong:
The example above checks tenantId is a string, then refuses the request without it. In a real app you would also confirm the authenticated user may use that tenant.
Pass threadId alongside forwardedProps when a screen can show more than one chat. Two instances with different thread ids keep separate histories.