Skip to content
EN DE

Custom Data Parts Streaming

Custom Data Parts let you send structured data alongside the LLM’s text stream. Instead of embedding status updates or metadata in the text, you write typed objects into the same stream. The frontend receives them as distinct parts it can render independently.

There are two kinds: Persistent Data Parts are saved in message.parts and survive across re-renders. Transient Data Parts are delivered only via the onData callback and are not stored in message history. Use persistent parts for content the user should see (progress, sources), and transient parts for ephemeral signals (loading indicators, analytics events).

On the server side, createUIMessageStreamResponse gives you a writer that accepts both standard stream events and custom data. You write data parts with writer.write() and merge the LLM stream with result.mergeIntoDataStream(writer).

import { createUIMessageStreamResponse, streamText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
export async function POST(req: Request) {
const { messages } = await req.json();
return createUIMessageStreamResponse({
execute(writer) {
// Persistent data part -- saved in message.parts
writer.write({
type: 'data',
data: { type: 'weather', city: 'Berlin', status: 'loading' },
});
const result = streamText({
model: anthropic('claude-sonnet-4-5-20250514'),
messages,
onFinish() {
writer.write({
type: 'data',
data: { type: 'weather', city: 'Berlin', status: 'done', temp: 22 },
});
},
});
result.mergeIntoDataStream(writer);
},
});
}
Method / FieldTypeDescription
writer.write({ type: 'data', data })PersistentSaved in message.parts as a DataUIPart
writer.write({ type: 'data', data, id })Persistent + IDEnables ID reconciliation (updates existing part)
result.mergeIntoDataStream(writer)Pipes the LLM stream into the writer

Transient parts are not stored in message history. Send them for ephemeral signals:

writer.write({
type: 'data',
data: { type: 'analytics', event: 'stream-started' },
});

The frontend receives them only in the onData callback of useChat, not in message.parts.

Part of AI Learning — free courses from prompt to production. Jan on LinkedIn