Skip to main content
Cedar provides a flexible storage system for managing chat messages with automatic persistence and automatic thread management. By default, Cedar uses no storage - no persistence unless you configure a storage adapter.
Storage Requirements: Message persistence requires both userId and threadId to be set. Without these IDs, storage operations are skipped and messages remain in memory only.

Quick Start

Cedar works out of the box with no storage (no persistence). To enable persistence, configure a storage adapter.

Storage Options

Cedar supports three storage adapters:
  1. Local Storage (default) - Browser localStorage
  2. No Storage - Disables persistence
  3. Custom Storage - Your own implementation
Configure storage by passing the messageStorage prop to CedarCopilot:

Additional Configuration Requirements by Storage Type

Local Storage & No Storage

No additional configuration needed - these options work out of the box.

Custom Storage

Implement your own storage solution by providing a custom adapter:

How Default Message Storage Works

Cedar preconfigures several storage methods that orchestrate your adapter’s functionality. Understanding these helps you work with Cedar’s storage system effectively.

Preconfigured Storage Methods

Cedar provides these methods out of the box. These methods are automatically called by Cedar at appropriate times - you don’t need to call them directly. To customize their behavior, you can override them in your implementation.

1. initializeChat({threadId?: string, userId?: string})

This method orchestrates the initial loading of threads and messages. It’s automatically called:
  • When the message storage adapter is first set
  • When userId or threadId changes in Cedar state
Here’s what it does with your adapter:

2. persistMessageStorageMessage(message)

This method handles message persistence with intelligent thread management:

3. sendMessage() Storage Actions

When a user sends a message, Cedar performs these storage operations:
Best practice: make your load endpoints idempotent. Implementations of listThreads or loadMessages should NOT perform any mutations such as creating threads or messages. Cedar may call these functions multiple times during normal operation; if they create data each time you could end up with duplicated threads or messages.

Automatic Behaviors

Thread & Message Loading:
  • When storage adapter is set: Adapter is configured but chat initialization happens separately
  • When user ID or thread ID changes: Triggers initializeChat which:
    • Loads all threads for the user
    • May auto-select first thread if none selected
    • Loads messages for the selected thread
  • When userId is undefined: Storage operations are skipped entirely
  • When threadId is undefined: A new UUID is generated during message persistence

User and Thread Management

User ID and thread ID are both stored as Cedar State variables and are automatically sent to the backend for Mastra and Custom backends. See Agent Backend Connection docs for more details.

Setting User ID

User ID is a Cedar State variable that can be set in two ways: Method 1: Initial Configuration (Recommended) Pass the user ID directly to CedarCopilot as a prop. This is the simplest and most common approach:
When you pass userId to CedarCopilot, it automatically: - Registers it as a Cedar State variable (null values become empty strings) - Triggers initializeChat which loads threads for that user - Makes it available throughout your app via Cedar State
Method 2: Dynamic Setting via State APIs Use this when you need to change users at runtime (e.g., after login, user switching):
When the user ID changes:
  1. Cedar loads threads for the new user
  2. If no thread is selected, may auto-select first thread (if listThreads is implemented)
  3. Messages are cleared and reloaded based on the user’s threads

Setting Thread ID

Thread ID is a Cedar State variable that can be set in three ways: Method 1: Initial Configuration Pass the thread ID directly to CedarCopilot as a prop. This is useful when you want to start with a specific thread:
Like userId, threadId is registered as a Cedar State variable with null values converted to empty strings. This triggers initializeChat to load messages for the specified thread.
Method 2: Explicit Setting (User Control) Use this when users manually select threads, navigate to specific conversations, or create new threads at runtime:
Method 3: Automatic Selection via listThreads If you implement the listThreads method in your storage adapter, Cedar will automatically select the first available thread when no thread is currently selected. This is perfect for app initialization:
When the thread ID changes (via any method), Cedar automatically:
  1. Clears the current messages from the UI
  2. Loads messages for the new thread from storage
  3. Updates the UI with the new thread’s messages

Next Steps