Skip to main content
Let users bookmark messages for later. Saving a message is private to the logged-in user — nobody else in the conversation can see it — and works across conversations: a user’s saved messages from all of their chats appear in one list, synced across all of their devices. Let’s see how to work with saved messages in CometChat’s Android SDK.
Saving a message with the SDK requires the Save Message feature to be enabled for your app. You can check its availability at runtime using the feature flag.

Save a Message

To save a message, use the saveMessage method and pass the ID of the message. On success, the callback returns the updated BaseMessage with its savedAt attribute set.
Saving is idempotent — saving an already saved message succeeds and refreshes getSavedAt() rather than failing.
A just-sent message may not be pinnable or savable yet. On an app with moderation enabled, the server stamps a new message as moderation-pending and clears it a moment later. While it is pending, the call is rejected with ERR_MESSAGE_NO_ACCESS — the same code returned for a genuine permission refusal, so you cannot tell the two apart from the error alone.The window runs from send, not from the user’s tap, and clears within a few seconds. Do not disable the control on this error: by the time someone opens a menu and taps, moderation has usually finished. Prefer a retry or a transient “not ready yet” message over telling the user they lack permission. Moderation is configured per app, so this never reproduces on an app that has it switched off. (The CometChat UI Kits already withhold the Pin/Save options on a moderation-pending, disapproved, deleted or not-yet-sent message; this only concerns custom UI built directly on the SDK.)
Unlike pinning, saving has no role restrictions — every user can save any message they have access to. Deleted messages cannot be saved.

Unsave a Message

To remove a message from the user’s saved list, use the unsaveMessage method. On success, the callback returns the updated BaseMessage with its savedAt attribute cleared.

Fetch Saved Messages

To fetch all messages the logged-in user has saved, create a MessagesRequest with the setSaved(true) filter of the MessagesRequestBuilder. Because saved messages are user-level and span conversations, you must not set a UID or GUID. The returned list is sorted by the time of saving, most recently saved first.
Starting with v5.1.0, the CallbackListener overload of `fetchNext()` is deprecated. Use `MessageFetchListener` instead; it requires SDK 5.1.0 or later, so on 5.0.x keep using CallbackListener. With offline support off, it behaves exactly like the old callback: onError is unchanged, and the server result arrives in the onServer… callback instead of onSuccess. With offline support on, it also delivers the cached result first; the old overload keeps working but receives only the server result. See Show Cached Data.
The list is ordered by save time, most recently saved first — not by when the messages were sent. Page it with fetchNext() until a call returns an empty list; the cursor rides on savedAt instead of sentAt, and the SDK swaps it for you.
If the user loses access to a conversation (for example, they are removed from a group), messages saved from it are cleaned up and no longer returned.

Check if a Message is Saved

Every fetched message carries the logged-in user’s save state on the BaseMessage itself. These values are per-user: the same message shows different values to different users.

Real-time Save Events

Because saving is private, save events are never delivered to other participants. Register a MessageListener and override the save callbacks; each event delivers the full updated BaseMessage. Your own save or unsave fires onMessageSaved / onMessageUnsaved on the device that performed it, just before the saveMessage() / unsaveMessage() success callback, so update your UI in one of the two, not both. These callbacks also fire on the user’s other devices when a save frame arrives there.
To stop listening, remove the listener with CometChat.removeMessageListener(listenerID).

Save Limit

A user can save a capped number of messages, configurable per app. Read the cap rather than hard-coding it — it is tenant-overridable and will drift.
The call is synchronous and never throws. It returns Settings.LIMIT_UNSPECIFIED (-1) when the app settings carry no value or when it is called before init() completes — show generic copy in that case rather than guessing a number. Unlike pinning, saving has no separate system cap. When the limit is breached, the SDK surfaces the server error through onError, and the applicable limit is carried in the exception’s errorParams:

Error Handling

Feature Availability

Check whether the Save Message feature is enabled for your app before showing save actions in your UI. The method is synchronous and safe to call from the UI layer.

Offline Behaviour

With offline support enabled, fetchNext() on a setSaved(true) request delivers a cached page first (onCachedMessages, even when empty) and then the server list (onServerMessages); offline, it ends with onError with code ERROR_INTERNET_UNAVAILABLE after the cached page. onCachedMessages holds the user’s cached saved messages across all conversations. The saved list is answered from its own store, so other MessagesRequestBuilder filters are ignored, as on the server.

Next Steps

Pin A Message

Highlight a message for everyone in the conversation

Pin A Conversation

Pin a conversation to the top of the list

All Real Time Listeners

Every listener the SDK exposes, in one place

Additional Message Filtering

Filter messages by pinned, saved, type, tags and more