Skip to main content
The chatbot has migrated from a content-based message structure to a parts-based message structure. This guide will help you understand the changes and migrate your existing code.

Overview

The original Message table used a simple content field to store message data. The new Message_v2 table introduces a parts field that supports multi-modal message content and better represents the structure of AI SDK messages.
The deprecated Message and Vote tables are marked for removal in a future version. You should migrate to Message_v2 and Vote_v2 as soon as possible.

What changed

1

Schema migration

The database schema has been updated to use a parts-based structure:
lib/db/schema.ts
// DEPRECATED: The following schema is deprecated and will be removed in the future.
export const messageDeprecated = pgTable("Message", {
  id: uuid("id").primaryKey().notNull().defaultRandom(),
  chatId: uuid("chatId")
    .notNull()
    .references(() => chat.id),
  role: varchar("role").notNull(),
  content: json("content").notNull(),
  createdAt: timestamp("createdAt").notNull(),
});

// NEW: Use this schema going forward
export const message = pgTable("Message_v2", {
  id: uuid("id").primaryKey().notNull().defaultRandom(),
  chatId: uuid("chatId")
    .notNull()
    .references(() => chat.id),
  role: varchar("role").notNull(),
  parts: json("parts").notNull(),
  attachments: json("attachments").notNull(),
  createdAt: timestamp("createdAt").notNull(),
});
The new schema replaces content with parts and adds an attachments field for file uploads.
2

Type updates

Update your type imports to use the new message type:
// Before
import type { MessageDeprecated } from "@/lib/db/schema";

// After
import type { DBMessage } from "@/lib/db/schema";
3

Message structure

Messages now use a parts array instead of a single content field:
// Before (deprecated)
{
  id: "msg-123",
  chatId: "chat-456",
  role: "assistant",
  content: "Hello! How can I help you today?",
  createdAt: new Date()
}

// After (new)
{
  id: "msg-123",
  chatId: "chat-456",
  role: "assistant",
  parts: [
    { type: "text", text: "Hello! How can I help you today?" }
  ],
  attachments: [],
  createdAt: new Date()
}

Migration steps

1

Update your database

Run the database migration to create the new tables:
pnpm db:migrate
This will create the Message_v2 and Vote_v2 tables alongside the existing tables.
2

Update message saving

Update your code to save messages with the parts structure. Here’s how it’s done in the chat API:
app/(chat)/api/chat/route.ts
await saveMessages({
  messages: [
    {
      chatId: id,
      id: message.id,
      role: "user",
      parts: message.parts,
      attachments: [],
      createdAt: new Date(),
    },
  ],
});
3

Update message retrieval

Convert database messages to UI messages using the utility function:
import { convertToUIMessages } from "@/lib/utils";

const messagesFromDb = await getMessagesByChatId({ id });
const uiMessages = convertToUIMessages(messagesFromDb);
4

Update vote references

If you’re using the voting feature, update to reference the new vote table:
// The vote table now references Message_v2 instead of Message
export const vote = pgTable(
  "Vote_v2",
  {
    chatId: uuid("chatId")
      .notNull()
      .references(() => chat.id),
    messageId: uuid("messageId")
      .notNull()
      .references(() => message.id),
    isUpvoted: boolean("isUpvoted").notNull(),
  },
  (table) => {
    return {
      pk: primaryKey({ columns: [table.chatId, table.messageId] }),
    };
  }
);

Parts structure

The parts field is an array that can contain different types of content:
{
  type: "text",
  text: "Your message content here"
}
{
  type: "tool-call",
  toolCallId: "call-123",
  toolName: "getWeather",
  args: { city: "San Francisco" }
}
{
  type: "tool-result",
  toolCallId: "call-123",
  toolName: "getWeather",
  result: {
    temperature: 72,
    condition: "sunny"
  }
}

Benefits of parts-based messages

The new parts-based structure provides several advantages:
  • Multi-modal support: Messages can contain multiple types of content (text, tool calls, tool results)
  • Better AI SDK alignment: The structure matches the AI SDK’s message format
  • Streaming support: Parts can be streamed individually for better UX
  • Tool integration: Tool calls and results are first-class message components
  • Type safety: TypeScript types are more accurate and helpful

Handling tool calls

With the new structure, tool calls are represented as message parts:
app/(chat)/api/chat/route.ts
onFinish: async ({ messages: finishedMessages }) => {
  await saveMessages({
    messages: finishedMessages.map((currentMessage) => ({
      id: currentMessage.id,
      role: currentMessage.role,
      parts: currentMessage.parts,
      createdAt: new Date(),
      attachments: [],
      chatId: id,
    })),
  });
}
The parts field automatically includes all text content, tool calls, and tool results from the AI SDK’s streaming response.

Backward compatibility

The deprecated tables will remain in the database until you’re ready to remove them. However:
  • New features will only support the Message_v2 structure
  • The deprecated tables are marked with DEPRECATED comments in the schema
  • You should plan to migrate all existing data and remove the old tables

Next steps

Build docs developers (and LLMs) love