> ## Documentation Index
> Fetch the complete documentation index at: https://www.cometchat.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Save A Message

> Save and unsave messages privately, fetch the saved list across conversations, and listen for save events with the CometChat JavaScript SDK.

Saving bookmarks a message for the logged-in user. Unlike a pin, a save is **private and cross-conversation**: nobody else can see it, no role is required, and the saved list spans every conversation the user is part of.

<Note>
  `savedAt` is per-viewer. It is only ever populated in the acting user's own
  context — you will never see another user's saves on a message.
</Note>

## Save a Message

Call `saveMessage()` with the message's ID. It resolves with the full updated message, with `savedAt` set.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    let messageId: number = 100;

    CometChat.saveMessage(messageId).then(
      (message: CometChat.BaseMessage) => {
        console.log("Message saved:", message);
      },
      (error: CometChat.CometChatException) => {
        console.log("Failed to save message:", error);
      }
    );
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    let messageId = 100;

    CometChat.saveMessage(messageId).then(
      (message) => {
        console.log("Message saved:", message);
      },
      (error) => {
        console.log("Failed to save message:", error);
      }
    );
    ```
  </Tab>
</Tabs>

Saving is idempotent — saving an already saved message succeeds rather than failing.

## Unsave a Message

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    CometChat.unsaveMessage(100).then(
      (message: CometChat.BaseMessage) => {
        console.log("Message unsaved:", message);
      },
      (error: CometChat.CometChatException) => {
        console.log("Failed to unsave message:", error);
      }
    );
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    CometChat.unsaveMessage(100).then(
      (message) => {
        console.log("Message unsaved:", message);
      },
      (error) => {
        console.log("Failed to unsave message:", error);
      }
    );
    ```
  </Tab>
</Tabs>

The resolved message comes back with `savedAt` cleared, never left stale.

## Fetch Saved Messages

Build a `MessagesRequest` with `setSaved(true)`. The saved list is **user-level**, so unlike the pinned list you do **not** set a UID or a GUID — leaving both unset is what makes it cross-conversation.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    let messagesRequest: CometChat.MessagesRequest =
      new CometChat.MessagesRequestBuilder()
        .setSaved(true)
        .setLimit(50)
        .build();

    messagesRequest.fetchPrevious().then(
      (messages: CometChat.BaseMessage[]) => {
        console.log("Saved messages:", messages);
      },
      (error: CometChat.CometChatException) => {
        console.log("Failed to fetch saved messages:", error);
      }
    );
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    let messagesRequest = new CometChat.MessagesRequestBuilder()
      .setSaved(true)
      .setLimit(50)
      .build();

    messagesRequest.fetchPrevious().then(
      (messages) => {
        console.log("Saved messages:", messages);
      },
      (error) => {
        console.log("Failed to fetch saved messages:", error);
      }
    );
    ```
  </Tab>
</Tabs>

The list comes back newest message first. Call `fetchPrevious()` again on the same object to page through older entries.

Because the list spans conversations, every row carries its own context — use `getConversationId()`, `getReceiverId()` and `getReceiverType()` to route a tap on a saved message back to the right conversation.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    messages.forEach((message: CometChat.BaseMessage) => {
      console.log(
        message.getConversationId(),
        message.getReceiverType(), // "user" or "group"
        message.getReceiverId()
      );
    });
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    messages.forEach((message) => {
      console.log(
        message.getConversationId(),
        message.getReceiverType(), // "user" or "group"
        message.getReceiverId()
      );
    });
    ```
  </Tab>
</Tabs>

## Check if a Message is Saved

The presence of `savedAt` *is* the boolean — an unsaved message simply has no save attribute.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    if (message.isSaved()) {
      console.log("Saved at:", message.getSavedAt());
    }
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    if (message.isSaved()) {
      console.log("Saved at:", message.getSavedAt());
    }
    ```
  </Tab>
</Tabs>

| Method         | Returns                                  |
| -------------- | ---------------------------------------- |
| `isSaved()`    | `true` when the logged-in user saved it. |
| `getSavedAt()` | The save timestamp, or `undefined`.      |

## Real-time Save Events

Save and unsave are **private multi-device** events: they are delivered to the user's other logged-in sessions so a save on the phone shows up on the desktop. Add the callbacks to your existing `MessageListener`.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    CometChat.addMessageListener(
      "UNIQUE_LISTENER_ID",
      new CometChat.MessageListener({
        onMessageSaved: (message: CometChat.BaseMessage) => {
          console.log("Message saved:", message);
        },
        onMessageUnsaved: (message: CometChat.BaseMessage) => {
          console.log("Message unsaved:", message);
        },
      })
    );
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    CometChat.addMessageListener(
      "UNIQUE_LISTENER_ID",
      new CometChat.MessageListener({
        onMessageSaved: (message) => {
          console.log("Message saved:", message);
        },
        onMessageUnsaved: (message) => {
          console.log("Message unsaved:", message);
        },
      })
    );
    ```
  </Tab>
</Tabs>

<Note>
  These callbacks fire for saves made on your **other** devices, not the one that
  performed the save — that device already has the message resolved from
  `saveMessage()`. Update your saved list from the promise there, and from these
  callbacks everywhere else.
</Note>

## Save Limit

A user may save a capped number of messages across all conversations. Read the cap from app settings rather than hard-coding it.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    let limit: number | null = await CometChat.getSavedMessagesLimit();

    if (limit !== null && savedCount >= limit) {
      // Disable the save control instead of letting the user hit the error
    }
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    CometChat.getSavedMessagesLimit().then((limit) => {
      if (limit !== null && savedCount >= limit) {
        // Disable the save control instead of letting the user hit the error
      }
    });
    ```
  </Tab>
</Tabs>

It resolves to `null` when the app settings carry no value — show generic copy in that case rather than guessing a number.

## Feature Availability

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    let enabled: boolean = await CometChat.isSaveMessageEnabled();

    if (enabled) {
      // Show the Save option
    }
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    CometChat.isSaveMessageEnabled().then((enabled) => {
      if (enabled) {
        // Show the Save option
      }
    });
    ```
  </Tab>
</Tabs>

This resolves `false` rather than rejecting when the flag is missing or settings are unavailable.

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Pin A Message" icon="thumbtack" href="/docs/sdk/javascript/pin-message">
    Highlight a message for everyone in a conversation
  </Card>

  <Card title="Pin A Conversation" icon="list" href="/docs/sdk/javascript/pin-conversation">
    Pin a conversation to the top of the list
  </Card>

  <Card title="Retrieve Conversations" icon="comments" href="/docs/sdk/javascript/retrieve-conversations">
    Fetch and order the conversation list
  </Card>

  <Card title="Additional Message Filtering" icon="filter" href="/docs/sdk/javascript/message-filtering">
    Filter messages by saved, pinned, type, tags and more
  </Card>
</CardGroup>
