> ## Documentation Index
> Fetch the complete documentation index at: https://cometchat-22654f5b-docs-llms-scoped-indexes.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Saved Messages

> A panel component listing the messages the logged-in user has saved, across every conversation

The `CometChatSavedMessages` component lists the messages the logged-in user has saved, newest first, gathered from every conversation they take part in. Saving is private: a saved message is visible only to the user who saved it, and no one else in the conversation is told.

## Overview

The Saved Messages panel provides:

* **Cross-conversation list**: Every save the user has made, regardless of which chat it came from
* **Conversation-style rows**: Each row shows the source conversation's avatar and name, with the speaker named in the subtitle — the same shape the conversation list uses
* **Rich previews**: Media messages name their type behind a matching icon, captions win over type labels, and thread replies are marked
* **Inline unsave**: A per-row unsave control, behind a confirmation dialog
* **Paged loading**: 30 rows per page, fetching the next page as you scroll
* **Live updates**: Saves and unsaves made elsewhere in the app are reflected without a refetch
* **Focus trap**: Traps keyboard focus within the panel for modal-like behavior

<Note>
  Save Message is gated by the `features.ux.messages.saved.enabled` app setting, which CometChat provisions server-side. Until it is on, `CometChat.isSaveMessageEnabled()` resolves `false` and the save surfaces do not render. See [Enabling Pin and Save](/ui-kit/angular/guides/pin-and-save-messages#enabling-the-feature) for the development override.
</Note>

<Info>
  **Live Preview** — saved messages drawn from several conversations, newest save first.
  [Open in Storybook ↗](https://storybook.cometchat.io/angular/?path=/story/components-messages-cometchat-saved-messages--default)
</Info>

<iframe src="https://storybook.cometchat.io/angular/iframe.html?id=components-messages-cometchat-saved-messages--default&viewMode=story&shortcuts=false&singleStory=true" className="w-full rounded-xl" loading="lazy" style={{height: "600px", border: "1px solid #e0e0e0"}} title="CometChat Saved Messages — Default" allow="clipboard-write" />

## Basic Usage

### Simple Saved Messages Panel

The panel is scoped to the logged-in user, so it takes no conversation input. Because a save spans every conversation, place it in your app chrome rather than in a chat header.

```typescript expandable theme={null}
import { Component } from '@angular/core';
import { CometChat } from '@cometchat/chat-sdk-javascript';
import { CometChatSavedMessagesComponent } from '@cometchat/chat-uikit-angular';

@Component({
  selector: 'app-saved-demo',
  standalone: true,
  imports: [CometChatSavedMessagesComponent],
  template: `
    <cometchat-saved-messages
      (messageClick)="onMessageClick($event)"
      (closeClick)="onClose()">
    </cometchat-saved-messages>
  `
})
export class SavedDemoComponent {
  /** Open the source conversation and jump to the message. */
  onMessageClick(message: CometChat.BaseMessage): void {
    console.log('open', message.getConversationId?.(), message.getId());
  }

  onClose(): void {
    console.log('Panel closed');
  }
}
```

### Read-Only Panel

Hide the unsave control when the panel is used purely for navigation.

```typescript expandable theme={null}
@Component({
  selector: 'app-saved-readonly-demo',
  standalone: true,
  imports: [CometChatSavedMessagesComponent],
  template: `
    <cometchat-saved-messages
      [hideUnsaveMessageOption]="true"
      (messageClick)="onMessageClick($event)">
    </cometchat-saved-messages>
  `
})
export class SavedReadonlyDemoComponent {
  onMessageClick(message: CometChat.BaseMessage): void {}
}
```

### Custom Empty State

```typescript expandable theme={null}
@Component({
  selector: 'app-saved-empty-demo',
  standalone: true,
  imports: [CometChatSavedMessagesComponent],
  template: `
    <cometchat-saved-messages [emptyView]="empty"></cometchat-saved-messages>

    <ng-template #empty>
      <div class="my-empty">Nothing saved yet — tap Save on any message.</div>
    </ng-template>
  `
})
export class SavedEmptyDemoComponent {}
```

## Filtering

Pass a `messagesRequestBuilder` to customize the fetch — the page size, most commonly. Call `setSaved(true)` on the builder: it is what scopes the request to the logged-in user's saved messages, and without it the request is an ordinary history read.

```typescript expandable theme={null}
import { Component } from '@angular/core';
import { CometChat } from '@cometchat/chat-sdk-javascript';
import { CometChatSavedMessagesComponent } from '@cometchat/chat-uikit-angular';

@Component({
  selector: 'app-saved-filtered',
  standalone: true,
  imports: [CometChatSavedMessagesComponent],
  template: `
    <cometchat-saved-messages
      [messagesRequestBuilder]="builder">
    </cometchat-saved-messages>
  `
})
export class SavedFilteredComponent {
  builder = new CometChat.MessagesRequestBuilder()
    .setSaved(true) // required — scopes the fetch to saved messages
    .setLimit(30);
}
```

<Note>
  The component re-asserts `setSaved(true)` on whatever builder you pass, so it is safe even if you omit it — but keep it in your code to make the intent explicit. Do not scope the builder to a UID or GUID: saves span every conversation.
</Note>

## Properties

| Property | Type | Default | Description |
| - | - | - | - |
| `hideUnsaveMessageOption` | `boolean` | `false` | Hides the per-row unsave control, making the list read-only |
| `hideCloseButton` | `boolean` | `false` | Hides the close button, for hosts that supply their own chrome |
| `headerView` | `TemplateRef<unknown>` | `undefined` | Replaces the default header row |
| `emptyView` | `TemplateRef<unknown>` | `undefined` | Replaces the built-in empty state |
| `errorView` | `TemplateRef<unknown>` | `undefined` | Replaces the built-in error state |
| `loadingView` | `TemplateRef<unknown>` | `undefined` | Replaces the built-in loading shimmer |
| `itemView` | `TemplateRef<{ $implicit: CometChat.BaseMessage; message: CometChat.BaseMessage }>` | `undefined` | Replaces a whole saved row. The message arrives as `$implicit` and again as `message`. A replaced row owns its own interaction |
| `textFormatters` | `CometChatTextFormatter[]` | `undefined` | Formatters applied to each row's preview text. Falls back to the global config's set when unset. See [Text Formatters](/ui-kit/angular/guides/custom-text-formatter) |
| `messagesRequestBuilder` | `CometChat.MessagesRequestBuilder` | `undefined` | Custom request builder. Used as supplied apart from `setSaved`, which is re-asserted. Do not scope it to a UID/GUID — saves span conversations |

## Events

| Event | Payload Type | Description |
| - | - | - |
| `closeClick` | `void` | Emitted when the panel close button is clicked, or Escape is pressed with no dialog open |
| `messageClick` | `CometChat.BaseMessage` | Emitted when a row is tapped. The host opens the source conversation and jumps to the message |
| `error` | `CometChat.CometChatException` | Emitted when fetching or unsaving fails |

## Behavior

### Privacy

`savedAt` is per-viewer: it is only ever populated on the acting user's own copy of a message. Saving a message therefore tells no one, and this panel never shows another user's saves. Save events arrive only on the acting user's own devices, which is what keeps a save in sync across their sessions.

### Row Identity

A saved message is shown under the conversation it came from — a group's name and icon, or the other party in a one-on-one — with the speaker named in the subtitle. Source names are resolved once and cached, and the raw ID shows until a name arrives, so a row is never withheld waiting on it.

### Confirmation

Unsaving asks for confirmation; saving does not.

### Pagination

The first page is 30 rows and the next is fetched as the list nears its end. A page that yields nothing new stops further loading, so a repeated page cannot spin.

## Customization

### CSS Variables

| Variable | Default | Description |
| - | - | - |
| `--cometchat-saved-messages-width` | `400px` | Panel width |
| `--cometchat-saved-messages-height` | `100%` | Panel height |
| `--cometchat-saved-messages-background` | `--cometchat-background-color-01` | Panel background |
| `--cometchat-saved-messages-border` | `1px solid --cometchat-border-color-light` | Leading edge border |
| `--cometchat-saved-messages-header-padding` | `12px 16px` | Header padding |
| `--cometchat-saved-messages-title-font` | `--cometchat-font-heading3-bold` | Header title font |
| `--cometchat-saved-messages-avatar-size` | `48px` | Row avatar size |
| `--cometchat-saved-messages-row-padding` | `8px 16px` | Row padding |
| `--cometchat-saved-messages-row-gap` | `--cometchat-spacing-3` | Gap between avatar, body, and trailing control |
| `--cometchat-saved-messages-row-background-hover` | `--cometchat-extended-primary-color-100` | Row hover background |
| `--cometchat-saved-messages-row-title-font` | `--cometchat-font-heading4-medium` | Source conversation name font |
| `--cometchat-saved-messages-row-sender-font` | `--cometchat-font-body-medium` | Sender prefix font |
| `--cometchat-saved-messages-row-preview-font` | `--cometchat-font-body-regular` | Preview text font |
| `--cometchat-saved-messages-preview-icon-size` | `16px` | Media-type icon size |
| `--cometchat-saved-messages-preview-icon-color` | `--cometchat-icon-color-secondary` | Media-type icon color |
| `--cometchat-saved-messages-thread-icon-size` | `12px` | Thread-reply marker size |
| `--cometchat-saved-messages-thread-icon-opacity` | `0.7` | Thread-reply marker opacity |
| `--cometchat-saved-messages-empty-icon-width` | `65px` | Empty-state illustration width |
| `--cometchat-saved-messages-empty-subtitle-gap` | `--cometchat-margin-2` | Gap between headline and explanation |
| `--cometchat-saved-messages-dialog-overlay-background` | `--cometchat-overlay-background` | Confirmation dialog scrim |

## Accessibility

### Keyboard Navigation

* **Escape** dismisses the confirmation dialog if one is open, otherwise the panel
* **Tab** cycles within the panel; focus does not escape to the page behind it
* **Enter** / **Space** on a row activates it, as a click does

### Focus Management

Focus is trapped on open and released on destroy, so the panel behaves as a modal surface while it is up.

### Screen Reader Support

* The panel is a labelled `region`
* Rows are exposed as buttons
* Loading, empty, and error states are announced via `role="status"`
* Media-type icons, thread markers, and illustrations are `aria-hidden`; the preview text carries the meaning

## Related

* [Pin and Save Messages](/ui-kit/angular/guides/pin-and-save-messages) — the feature guide, including how to enable it
* [CometChatPinnedMessages](/ui-kit/angular/components/cometchat-pinned-messages) — the conversation-wide counterpart
* [CometChatConversations](/ui-kit/angular/components/cometchat-conversations) — whose row shape this panel follows
* [CometChatMessageList](/ui-kit/angular/components/cometchat-message-list) — where messages are saved from


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.