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).


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:

MethodEndpointDescription
GET/api/threadsList threads
POST/api/threadsCreate thread
GET/api/threads/:idGet thread with messages
PATCH/api/threads/:idUpdate thread
DELETE/api/threads/:idDelete 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

PropTypeDescription
configWelcomeConfigTitle, subtitle, logo, and display settings
suggestionsstring[]Clickable suggestion prompts
recentThreadsThread[]Recent conversations to display
onSendMessage(message, attachments?) => voidCalled when user sends a message
onSelectThread(threadId) => voidCalled when user selects a thread
onDeleteThread(threadId) => voidCalled when user deletes a thread
attachmentsEnabledbooleanEnable 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
}

On this page