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

# Pin A Message

Keep important messages easy to find by pinning them to a conversation. A pinned message is visible to **all participants** of the conversation, along with who pinned it and when. Users can pin messages, unpin them, and fetch all pinned messages of a conversation. You can also listen to pin events in real-time. Let's see how to work with pinned messages in CometChat's Android SDK.

<Note>
  Pinning a message with the SDK requires the Pin Message feature to be enabled for your app. You can check its availability at runtime using the [feature flag](#feature-availability).
</Note>

## Pin a Message

To pin a message, use the `pinMessage` method and pass the ID of the message to be pinned. On success, the callback returns the updated `BaseMessage` with its pin attributes set.

<Tabs>
  <Tab title="Java">
    ```java theme={null}
    long messageId = 1;

    CometChat.pinMessage(messageId, new CometChat.CallbackListener<BaseMessage>() {
      @Override
      public void onSuccess(BaseMessage message) {
          Log.d(TAG, "Message pinned at: " + message.getPinnedAt());
      }

      @Override
      public void onError(CometChatException e) {
          Log.e(TAG, "Failed to pin message: " + e.getMessage());
      }
    });
    ```
  </Tab>

  <Tab title="Kotlin">
    ```kotlin theme={null}
    val messageId = 1L

    CometChat.pinMessage(messageId, object : CometChat.CallbackListener<BaseMessage>() {
      override fun onSuccess(message: BaseMessage?) {
          Log.d(TAG, "Message pinned at: ${message?.pinnedAt}")
      }

      override fun onError(e: CometChatException?) {
          Log.e(TAG, "Failed to pin message: ${e?.message}")
      }
    })
    ```
  </Tab>
</Tabs>

<Info>
  In a **group conversation**, the CometChat UI Kits show the Pin/Unpin option only to participants with the **Admin** or **Moderator** scope, or the group **owner**. This gate is applied client-side — the SDK does not currently enforce roles on the server, so apply your own role check if you build custom pin UI. Every participant can see pinned messages. In a **one-on-one conversation**, both participants can pin and unpin. Deleted messages cannot be pinned; deleting a pinned message automatically unpins it.
</Info>

## Unpin a Message

To unpin a message, use the `unpinMessage` method. Any participant with pin permission can unpin a message — not just the user who originally pinned it. On success, the callback returns the updated `BaseMessage` with its pin attributes cleared.

<Tabs>
  <Tab title="Java">
    ```java theme={null}
    long messageId = 1;

    CometChat.unpinMessage(messageId, new CometChat.CallbackListener<BaseMessage>() {
      @Override
      public void onSuccess(BaseMessage message) {
          Log.d(TAG, "Message unpinned. isPinned: " + message.isPinned());
      }

      @Override
      public void onError(CometChatException e) {
          Log.e(TAG, "Failed to unpin message: " + e.getMessage());
      }
    });
    ```
  </Tab>

  <Tab title="Kotlin">
    ```kotlin theme={null}
    val messageId = 1L

    CometChat.unpinMessage(messageId, object : CometChat.CallbackListener<BaseMessage>() {
      override fun onSuccess(message: BaseMessage?) {
          Log.d(TAG, "Message unpinned. isPinned: ${message?.isPinned}")
      }

      override fun onError(e: CometChatException?) {
          Log.e(TAG, "Failed to unpin message: ${e?.message}")
      }
    })
    ```
  </Tab>
</Tabs>

## Fetch Pinned Messages

To fetch all pinned messages of a conversation, create a `MessagesRequest` with the `setPinned(true)` filter of the `MessagesRequestBuilder`. Setting a `UID` (for a one-on-one conversation) or a `GUID` (for a group) is **mandatory** — exactly one of the two. The returned list is sorted by the time of pinning, most recently pinned first.

<Tabs>
  <Tab title="Java (User)">
    ```java theme={null}
    String UID = "cometchat-uid-1";

    MessagesRequest messagesRequest = new MessagesRequest.MessagesRequestBuilder()
      .setPinned(true)
      .setLimit(50)
      .setUID(UID)
      .build();

    messagesRequest.fetchNext(new CometChat.CallbackListener<List<BaseMessage>>() {
      @Override
      public void onSuccess(List<BaseMessage> messages) {
          Log.d(TAG, "Pinned messages: " + messages.size());
      }

      @Override
      public void onError(CometChatException e) {
          Log.e(TAG, "Pinned messages fetch failed: " + e.getMessage());
      }
    });
    ```
  </Tab>

  <Tab title="Java (Group)">
    ```java theme={null}
    String GUID = "cometchat-guid-1";

    MessagesRequest messagesRequest = new MessagesRequest.MessagesRequestBuilder()
      .setPinned(true)
      .setLimit(50)
      .setGUID(GUID)
      .build();

    messagesRequest.fetchNext(new CometChat.CallbackListener<List<BaseMessage>>() {
      @Override
      public void onSuccess(List<BaseMessage> messages) {
          Log.d(TAG, "Pinned messages: " + messages.size());
      }

      @Override
      public void onError(CometChatException e) {
          Log.e(TAG, "Pinned messages fetch failed: " + e.getMessage());
      }
    });
    ```
  </Tab>

  <Tab title="Kotlin (User)">
    ```kotlin theme={null}
    val UID = "cometchat-uid-1"

    val messagesRequest = MessagesRequest.MessagesRequestBuilder()
      .setPinned(true)
      .setLimit(50)
      .setUID(UID)
      .build()

    messagesRequest.fetchNext(object : CometChat.CallbackListener<List<BaseMessage>>() {
      override fun onSuccess(messages: List<BaseMessage>?) {
          Log.d(TAG, "Pinned messages: ${messages?.size}")
      }

      override fun onError(e: CometChatException?) {
          Log.e(TAG, "Pinned messages fetch failed: ${e?.message}")
      }
    })
    ```
  </Tab>

  <Tab title="Kotlin (Group)">
    ```kotlin theme={null}
    val GUID = "cometchat-guid-1"

    val messagesRequest = MessagesRequest.MessagesRequestBuilder()
      .setPinned(true)
      .setLimit(50)
      .setGUID(GUID)
      .build()

    messagesRequest.fetchNext(object : CometChat.CallbackListener<List<BaseMessage>>() {
      override fun onSuccess(messages: List<BaseMessage>?) {
          Log.d(TAG, "Pinned messages: ${messages?.size}")
      }

      override fun onError(e: CometChatException?) {
          Log.e(TAG, "Pinned messages fetch failed: ${e?.message}")
      }
    })
    ```
  </Tab>
</Tabs>

## Check if a Message is Pinned

Every fetched or received message carries its pin state on the `BaseMessage` itself.

| Method          | Description                                                                                                                                  |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `isPinned()`    | Returns `true` if the message is currently pinned in its conversation.                                                                       |
| `getPinnedAt()` | The timestamp at which the message was pinned. `0` when the message is not pinned.                                                           |
| `getPinnedBy()` | The `UID` of the user who most recently pinned the message. The value `app_system` indicates a pin applied by the app itself (a system pin). |

<Tabs>
  <Tab title="Java">
    ```java theme={null}
    if (message.isPinned()) {
        Log.d(TAG, "Pinned by " + message.getPinnedBy() + " at " + message.getPinnedAt());
    }
    ```
  </Tab>

  <Tab title="Kotlin">
    ```kotlin theme={null}
    if (message.isPinned) {
        Log.d(TAG, "Pinned by ${message.pinnedBy} at ${message.pinnedAt}")
    }
    ```
  </Tab>
</Tabs>

<Info>
  Editing a message preserves its pin. A message stores only its most recent pinner in `getPinnedBy()`.
</Info>

## Real-time Pin Events

Register a `MessageListener` and override the pin callbacks. Each event delivers the full updated `BaseMessage`, so you can directly replace the message in your list.

Today these callbacks fire on the **acting user's device** when a pin or unpin succeeds. Delivery to other participants activates once server-side real-time delivery for pin events is rolled out — until then, other clients pick up pin changes on their next message fetch.

<Tabs>
  <Tab title="Java">
    ```java theme={null}
    private String listenerID = "UNIQUE_LISTENER_ID";

    CometChat.addMessageListener(listenerID, new CometChat.MessageListener() {
      @Override
      public void onMessagePinned(BaseMessage message) {
          Log.d(TAG, "Message pinned: " + message.getId());
      }

      @Override
      public void onMessageUnpinned(BaseMessage message) {
          Log.d(TAG, "Message unpinned: " + message.getId());
      }
    });
    ```
  </Tab>

  <Tab title="Kotlin">
    ```kotlin theme={null}
    val listenerID = "UNIQUE_LISTENER_ID"

    CometChat.addMessageListener(listenerID, object : CometChat.MessageListener() {
      override fun onMessagePinned(message: BaseMessage) {
          Log.d(TAG, "Message pinned: ${message.id}")
      }

      override fun onMessageUnpinned(message: BaseMessage) {
          Log.d(TAG, "Message unpinned: ${message.id}")
      }
    })
    ```
  </Tab>
</Tabs>

To stop listening, remove the listener with `CometChat.removeMessageListener(listenerID)`.

## Pin Limit

A conversation can hold a limited number of pinned messages (100 by default). When the limit is exceeded, the SDK surfaces the server error through `onError`, and the applicable limit can be read programmatically from the exception — never hard-code it.

<Tabs>
  <Tab title="Java">
    ```java theme={null}
    @Override
    public void onError(CometChatException e) {
        Object limit = e.getErrorParams() != null ? e.getErrorParams().get("limit") : null;
        if (limit != null) {
            Log.e(TAG, "You can pin up to " + limit + " messages in a conversation.");
        }
    }
    ```
  </Tab>

  <Tab title="Kotlin">
    ```kotlin theme={null}
    override fun onError(e: CometChatException?) {
        val limit = e?.errorParams?.get("limit")
        if (limit != null) {
            Log.e(TAG, "You can pin up to $limit messages in a conversation.")
        }
    }
    ```
  </Tab>
</Tabs>

## Feature Availability

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

<Tabs>
  <Tab title="Java">
    ```java theme={null}
    if (CometChat.isPinMessageEnabled()) {
        // show the Pin option
    }
    ```
  </Tab>

  <Tab title="Kotlin">
    ```kotlin theme={null}
    if (CometChat.isPinMessageEnabled()) {
        // show the Pin option
    }
    ```
  </Tab>
</Tabs>
