Use Drizzle ORM to persist Sidekick conversations. This guide uses Postgres, but the schema translates easily to MySQL or SQLite.
1. Install Drizzle
bun add drizzle-orm pg
bun add -D drizzle-kit2. Define a chat schema
import {
pgTable,
pgEnum,
text,
uuid,
timestamp,
jsonb,
index,
} from "drizzle-orm/pg-core";
export const messageRole = pgEnum("message_role", ["user", "assistant"]);
export const chatThreads = pgTable("chat_threads", {
id: uuid("id").defaultRandom().primaryKey(),
title: text("title"),
createdAt: timestamp("created_at", { withTimezone: true })
.defaultNow()
.notNull(),
});
export const chatMessages = pgTable(
"chat_messages",
{
id: uuid("id").defaultRandom().primaryKey(),
threadId: uuid("thread_id")
.notNull()
.references(() => chatThreads.id, { onDelete: "cascade" }),
role: messageRole("role").notNull(),
content: text("content").notNull(),
attachments: jsonb("attachments"),
createdAt: timestamp("created_at", { withTimezone: true })
.defaultNow()
.notNull(),
},
(table) => ({
threadIndex: index("chat_messages_thread_idx").on(
table.threadId,
table.createdAt
),
})
);Store PromptInput attachments in attachments (JSON) or keep only metadata and upload the files to object storage.
3. Read messages for Sidekick
import { db } from "@/lib/db";
import { chatMessages } from "@/lib/schema";
import { asc, eq } from "drizzle-orm";
const messages = await db
.select()
.from(chatMessages)
.where(eq(chatMessages.threadId, threadId))
.orderBy(asc(chatMessages.createdAt));
return (
<Conversation>
<ConversationContent>
{messages.map((msg) => (
<Message key={msg.id} from={msg.role}>
<MessageContent from={msg.role}>{msg.content}</MessageContent>
</Message>
))}
</ConversationContent>
</Conversation>
);4. Save a new message
import type { PromptInputMessage } from "@/components/ui/prompt-input";
async function saveMessage(threadId: string, message: PromptInputMessage) {
if (!message.text.trim()) return;
await db.insert(chatMessages).values({
threadId,
role: "user",
content: message.text,
attachments: message.files,
});
}5. Stream assistant replies
When you stream the assistant response, update the assistant row as tokens arrive so Sidekick can reload the latest content on refresh.