Chat History
Save and restore chat conversations across sessions
Save and persist chat threads and messages so users can continue conversations across browser sessions, devices, or after logging out.
Browser Storage
For simple browser-level persistence without server setup:
<CopilotChat
persistence={true}
showThreadPicker={true}
/>Data is stored in localStorage (~5MB limit, single device only).
Storage quota and auto-eviction
When the browser's localStorage quota is exceeded (typically after many long conversations), the SDK automatically evicts the oldest threads to make room for new ones. The most recent threads are always preserved.
This eviction happens silently — users won't see an error. If you need to monitor or control this behavior, you can listen to the onStorageEviction callback:
<CopilotChat
persistence={true}
onStorageEviction={(evictedThreadIds) => {
console.log('Evicted old threads:', evictedThreadIds);
}}
/>Auto-eviction only applies to browser localStorage. Server persistence has no such limit — use it when conversation history must be retained long-term.
Server Persistence
Store threads in your own database for cross-device sync and user accounts.
<CopilotChat
persistence={{
type: "server",
endpoint: "/api/threads",
headers: { Authorization: `Bearer ${token}` },
}}
showThreadPicker={true}
/>API Contract
Your endpoint must implement these routes:
| Method | Endpoint | Description |
|---|---|---|
GET | /api/threads | List threads |
POST | /api/threads | Create thread |
GET | /api/threads/:id | Get thread with messages |
PATCH | /api/threads/:id | Update thread |
DELETE | /api/threads/:id | Delete thread |
Implementation
// app/api/threads/route.ts
export async function GET() {
const threads = await db.thread.findMany({
orderBy: { updatedAt: 'desc' },
});
return Response.json({ threads, total: threads.length, hasMore: false });
}
export async function POST(request: Request) {
const body = await request.json();
const thread = await db.thread.create({ data: body });
return Response.json(thread, { status: 201 });
}// app/api/threads/[id]/route.ts
import { NextRequest } from 'next/server';
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const thread = await db.thread.findUnique({ where: { id } });
if (!thread) return Response.json({ error: 'Not found' }, { status: 404 });
return Response.json(thread);
}
export async function PATCH(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const updates = await request.json();
const thread = await db.thread.update({ where: { id }, data: updates });
return Response.json(thread);
}
export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
await db.thread.delete({ where: { id } });
return new Response(null, { status: 204 });
}import { Router } from 'express';
const router = Router();
router.get('/', async (req, res) => {
const threads = await db.thread.findMany();
res.json({ threads, total: threads.length, hasMore: false });
});
router.post('/', async (req, res) => {
const thread = await db.thread.create({ data: req.body });
res.status(201).json(thread);
});
router.get('/:id', async (req, res) => {
const thread = await db.thread.findUnique({ where: { id: req.params.id } });
if (!thread) return res.status(404).json({ error: 'Not found' });
res.json(thread);
});
router.patch('/:id', async (req, res) => {
const thread = await db.thread.update({
where: { id: req.params.id },
data: req.body,
});
res.json(thread);
});
router.delete('/:id', async (req, res) => {
await db.thread.delete({ where: { id: req.params.id } });
res.status(204).send();
});
export default router;useThreadManager Hook
Access thread management functions directly for custom UIs:
import { useThreadManager } from '@yourgpt/copilot-sdk/react';
function CustomThreadList() {
const {
threads, // All threads
currentThread, // Currently active thread
currentThreadId, // Current thread ID
isLoading, // Loading state
createThread, // Create new thread
switchThread, // Switch to a thread
deleteThread, // Delete a thread
updateCurrentThread, // Update current thread
} = useThreadManager();
return (
<div>
{threads.map(thread => (
<div key={thread.id}>
<span onClick={() => switchThread(thread.id)}>
{thread.title || 'Untitled'}
</span>
<button onClick={() => deleteThread(thread.id)}>Delete</button>
</div>
))}
<button onClick={() => createThread()}>New Thread</button>
</div>
);
}Thread Object
interface Thread {
id: string;
title?: string;
preview?: string;
messageCount?: number;
createdAt?: Date;
updatedAt?: Date;
messages?: Message[];
}Custom Adapters
Create custom storage adapters for any backend:
import { useThreadManager } from '@yourgpt/copilot-sdk/react';
import type { ThreadStorageAdapter } from '@yourgpt/copilot-sdk/react';
const customAdapter: ThreadStorageAdapter = {
async getThreads() {
const response = await fetch('/api/my-threads');
return response.json();
},
async getThread(id) {
const response = await fetch(`/api/my-threads/${id}`);
return response.json();
},
async createThread(thread) {
const response = await fetch('/api/my-threads', {
method: 'POST',
body: JSON.stringify(thread),
});
return response.json();
},
async updateThread(id, updates) {
const response = await fetch(`/api/my-threads/${id}`, {
method: 'PATCH',
body: JSON.stringify(updates),
});
return response.json();
},
async deleteThread(id) {
await fetch(`/api/my-threads/${id}`, { method: 'DELETE' });
},
};
// Use with hook
const threadManager = useThreadManager({ adapter: customAdapter });
// Or use built-in server adapter
import { createServerAdapter } from '@yourgpt/copilot-sdk/react';
const serverAdapter = createServerAdapter({
endpoint: '/api/threads',
headers: { Authorization: `Bearer ${token}` },
});ChatWelcome Component
Display a welcome screen when starting a new conversation, with suggestions and recent chat history:
import { ChatWelcome } from '@yourgpt/copilot-sdk/ui';
function WelcomeScreen() {
const { sendMessage, threads, switchThread } = useCopilot();
return (
<ChatWelcome
config={{
title: "How can I help you today?",
subtitle: "Ask anything and get it done.",
logo: "/logo.png",
showRecentChats: true,
maxRecentChats: 3,
}}
suggestions={[
"Help me write an email",
"Explain this code",
"Create a marketing plan",
]}
recentThreads={threads}
onSendMessage={sendMessage}
onSelectThread={switchThread}
/>
);
}ChatWelcome Props
| Prop | Type | Description |
|---|---|---|
config | WelcomeConfig | Title, subtitle, logo, and display settings |
suggestions | string[] | Clickable suggestion prompts |
recentThreads | Thread[] | Recent conversations to display |
onSendMessage | (message, attachments?) => void | Called when user sends a message |
onSelectThread | (threadId) => void | Called when user selects a thread |
onDeleteThread | (threadId) => void | Called when user deletes a thread |
attachmentsEnabled | boolean | Enable file attachments (default: true) |
WelcomeConfig
interface WelcomeConfig {
title?: string; // Main heading
subtitle?: string; // Subheading
logo?: string; // Logo image URL
showRecentChats?: boolean; // Show recent threads
recentChatsLabel?: string; // Label for recent chats section
maxRecentChats?: number; // Max threads to show (default: 3)
suggestionsLabel?: string; // Label for suggestions
}