Message Metadata
Overview
Section titled “Overview”UIMessage has an optional metadata field — an arbitrary object attached to the message that is never sent to the LLM. It is invisible to the model but accessible in your application code for tracking, analytics, and display purposes.
Use metadata for information that describes the message: who sent it, when, which model generated it, session IDs, or custom flags. This is different from Data Parts, which are content within the message (progress indicators, source cards, structured results).
On the server side, you attach metadata to assistant responses via messageMetadata in toUIMessageStreamResponse or createUIMessageStreamResponse. On the client side, the metadata arrives on the message object and can be used for conditional rendering or logging.
Client-Side: UIMessage with Metadata
Section titled “Client-Side: UIMessage with Metadata”import type { UIMessage } from 'ai';
const message: UIMessage<{ userId: string; timestamp: number }> = { id: 'msg-1', role: 'user', parts: [{ type: 'text', text: 'Explain streaming.' }], metadata: { userId: 'user-42', timestamp: Date.now(), },};Server-Side: Response Metadata
Section titled “Server-Side: Response Metadata”import { streamText } from 'ai';import { anthropic } from '@ai-sdk/anthropic';
export async function POST(req: Request) { const { messages } = await req.json();
const result = streamText({ model: anthropic('claude-sonnet-4-5-20250514'), messages, });
return result.toUIMessageStreamResponse({ messageMetadata: { modelId: 'claude-sonnet-4-5-20250514', generatedAt: Date.now(), }, });}Metadata vs Data Parts
Section titled “Metadata vs Data Parts”| Aspect | Metadata | Data Parts |
|---|---|---|
| Purpose | Describes the message | Content within the message |
| Visible to LLM | No | No |
| Stored in | message.metadata | message.parts |
| Rendered in UI | Typically not (used for logic) | Yes (cards, progress, etc.) |
| Examples | userId, timestamp, modelId | Weather card, progress bar, sources |
Type Safety
Section titled “Type Safety”Use the UIMessage generic to type your metadata:
import type { UIMessage } from 'ai';
interface MessageMeta { userId: string; timestamp: number; modelId?: string;}
// Typed metadatatype AppMessage = UIMessage<MessageMeta>;In useChat, you can access typed metadata:
const { messages } = useChat();
messages.forEach((m) => { const meta = m.metadata as MessageMeta; if (meta?.modelId) { console.log(`Generated by: ${meta.modelId}`); }});See also
Section titled “See also”- Challenge 7.2: Message Metadata — hands-on metadata exercise
- Challenge 4.3: Persistence — storing messages with metadata