content/docs/04-ai-sdk-ui/25-message-metadata.mdx
Message metadata allows you to attach custom information to messages at the message level. This is useful for tracking timestamps, model information, token usage, user context, and other message-level data.
Message metadata differs from data parts in that it's attached at the message level rather than being part of the message content. While data parts are ideal for dynamic content that forms part of the message, metadata is perfect for information about the message itself.
Here's a simple example of using message metadata to track timestamps and model information:
First, define your metadata type for type safety:
import { UIMessage } from 'ai';
import { z } from 'zod';
// Define your metadata schema
export const messageMetadataSchema = z.object({
createdAt: z.number().optional(),
model: z.string().optional(),
totalTokens: z.number().optional(),
});
export type MessageMetadata = z.infer<typeof messageMetadataSchema>;
// Create a typed UIMessage
export type MyUIMessage = UIMessage<MessageMetadata>;
Use the messageMetadata callback in toUIMessageStreamResponse to send metadata at different streaming stages:
import { convertToModelMessages, streamText } from 'ai';
__PROVIDER_IMPORT__;
import type { MyUIMessage } from '@/types';
export async function POST(req: Request) {
const { messages }: { messages: MyUIMessage[] } = await req.json();
const result = streamText({
model: __MODEL__,
messages: await convertToModelMessages(messages),
});
return result.toUIMessageStreamResponse({
originalMessages: messages, // pass this in for type-safe return objects
messageMetadata: ({ part }) => {
// Send metadata when streaming starts
if (part.type === 'start') {
return {
createdAt: Date.now(),
model: 'your-model-id',
};
}
// Send additional metadata when streaming completes
if (part.type === 'finish') {
return {
totalTokens: part.totalUsage.totalTokens,
};
}
},
});
}
Access metadata through the message.metadata property:
'use client';
import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';
import type { MyUIMessage } from '@/types';
export default function Chat() {
const { messages } = useChat<MyUIMessage>({
transport: new DefaultChatTransport({
api: '/api/chat',
}),
});
return (
<div>
{messages.map(message => (
<div key={message.id}>
<div>
{message.role === 'user' ? 'User: ' : 'AI: '}
{message.metadata?.createdAt && (
<span className="text-sm text-gray-500">
{new Date(message.metadata.createdAt).toLocaleTimeString()}
</span>
)}
</div>
{message.parts.map((part, index) =>
part.type === 'text' ? <div key={index}>{part.text}</div> : null,
)}
{message.metadata?.totalTokens && (
<div className="text-xs text-gray-400">
{message.metadata.totalTokens} tokens
</div>
)}
</div>
))}
</div>
);
}
Message metadata is ideal for: