> ## 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.

# Pin A Conversation

> Pin and unpin conversations, read the pin-ordered conversation list, and listen for conversation pin events with the CometChat Flutter SDK.

<Accordion title="AI Integration Quick Reference">
  | Field | Value |
  | - | - |
  | Package | `cometchat_sdk` |
  | Import | `import 'package:cometchat_sdk/cometchat_sdk.dart';` |
  | Purpose | Pin and unpin conversations, read the pin-ordered conversation list, and listen for conversation pin events with the CometChat Flutter SDK. |
  | Key methods | `pinConversation()` · `unpinConversation()` · `isPinConversationEnabled()` · `getPinnedConversationsLimit()` · `addConversationListener()` · `removeConversationListener()` |
  | Key classes | `Conversation` · `ConversationListener` · `CometChatConversationType` · `CometChatException` |
  | Key fields | `Conversation.pinnedAt` · `Conversation.pinnedBy` |
  | Listener callbacks | `onConversationPinned()` · `onConversationUnpinned()` |
  | Prerequisites | SDK initialised via [`CometChat.init()`](/docs/sdk/flutter/setup) and a logged-in user via [`CometChat.login()`](/docs/sdk/flutter/authentication-overview). |
  | Constraints | A conversation is addressed by its peer UID/GUID plus the conversation type, not by `conversationId`. Pins are private to the logged-in user. |
  | Related | [Retrieve Conversations](/docs/sdk/flutter/retrieve-conversations) · [Pin A Message](/docs/sdk/flutter/pin-message) |
  | Full reference | [`Conversation`](/docs/sdk/reference/entities#conversation) · [`CometChatException`](/docs/sdk/reference/auxiliary#cometchatexception) |
</Accordion>

<Note>
  **Available from Chat SDK v5.0.7.** These APIs require `cometchat_sdk` v5.0.7 or later. See [Setup](/docs/sdk/flutter/setup) to upgrade.
</Note>

Pinning a conversation keeps it at the top of the logged-in user's conversation list. The pin is **private to that user** — nobody else sees it — and it syncs to their other devices. A pinned conversation carries a `pinnedAt` timestamp and the `pinnedBy` UID.

<Note>
  This is separate from an **admin (system) pin**, which is applied from an admin surface such as the [CometChat Dashboard](https://app.cometchat.com) and carries the `app_system` sentinel in `pinnedBy`. System pins rank above the user's own pins and cannot be created or removed from the SDK, only observed.
</Note>

## Pin a Conversation

A conversation is addressed by its peer — the other user's UID for a one-on-one conversation, or the GUID for a group — together with the conversation type.

<Tabs>
  <Tab title="Dart">
    ```dart theme={null}
    String conversationWith = "cometchat-uid-1";
    String conversationType = CometChatConversationType.user;

    CometChat.pinConversation(conversationWith, conversationType,
        onSuccess: (Conversation conversation) {
      debugPrint("Conversation pinned at: ${conversation.pinnedAt}");
    }, onError: (CometChatException e) {
      debugPrint("Conversation pinning failed with exception: ${e.message}");
    });
    ```
  </Tab>
</Tabs>

On success, `onSuccess` receives the full updated `Conversation`, with `pinnedAt` and `pinnedBy` set. Pinning is idempotent.

<Note>
  Addressing by peer rather than by `conversationId` is deliberate: it lets you pin a conversation that has no messages yet.
</Note>

## Unpin a Conversation

<Tabs>
  <Tab title="Dart">
    ```dart theme={null}
    String conversationWith = "cometchat-uid-1";
    String conversationType = CometChatConversationType.user;

    CometChat.unpinConversation(conversationWith, conversationType,
        onSuccess: (Conversation conversation) {
      debugPrint("Conversation unpinned");
    }, onError: (CometChatException e) {
      debugPrint("Conversation unpinning failed with exception: ${e.message}");
    });
    ```
  </Tab>
</Tabs>

The returned `Conversation` carries its pin fields cleared to `null`. Only a pin the logged-in user placed can be removed — a system pin is rejected server-side, so hide or disable the unpin control for conversations whose `pinnedBy` is `app_system`.

## Fetch Pinned Conversations

The default conversation list is already **pin-ordered** by the server: system pins first, then the user's own pins, then everything else by latest activity. Fetch it with the regular `ConversationsRequest` described in [Retrieve Conversations](/docs/sdk/flutter/retrieve-conversations) — no extra filter is needed.

<Tabs>
  <Tab title="Dart">
    ```dart theme={null}
    ConversationsRequest conversationsRequest = (ConversationsRequestBuilder()
          ..limit = 30)
        .build();

    conversationsRequest.fetchNext(onSuccess: (List<Conversation> conversations) {
      final pinned = conversations.where((c) => c.pinnedAt != null);
      debugPrint("Pinned conversations on this page: ${pinned.length}");
    }, onError: (CometChatException e) {
      debugPrint("Conversation fetching failed with exception: ${e.message}");
    });
    ```
  </Tab>
</Tabs>

When you apply real-time events to a list you already hold, keep the same ordering contract: system pins stay above user pins, and user pins stay above the activity-ordered rest of the list — new activity moves a conversation to the top of **its own section only**.

## Check if a Conversation is Pinned

As with messages, a `null` `pinnedAt` means the conversation is not pinned.

| Field | Type | Description |
| - | - | - |
| `pinnedAt` | `DateTime?` | When the conversation was pinned for the logged-in user, or `null`. |
| `pinnedBy` | `String?` | The UID of whoever placed the pin, `app_system` for a system pin, or `null` when not pinned. |

<Tabs>
  <Tab title="Dart">
    ```dart theme={null}
    if (conversation.pinnedAt != null) {
      final bool isSystemPin = conversation.pinnedBy == "app_system";
      debugPrint("Pinned at: ${conversation.pinnedAt}, system pin: $isSystemPin");
    }
    ```
  </Tab>
</Tabs>

## Real-time Conversation Pin Events

Conversation pins arrive on a dedicated `ConversationListener`, **not** on `MessageListener` — the payload is a `Conversation`, not a message. Register it with `addConversationListener()` and override `onConversationPinned()` and `onConversationUnpinned()`.

<Tabs>
  <Tab title="Dart">
    ```dart theme={null}
    class Class_Name with ConversationListener {

      //CometChat.addConversationListener("listenerId", this);

      @override
      void onConversationPinned(Conversation conversation) {
        debugPrint("Conversation pinned: ${conversation.conversationId}");
      }

      @override
      void onConversationUnpinned(Conversation conversation) {
        debugPrint("Conversation unpinned: ${conversation.conversationId}");
      }
    }
    ```
  </Tab>
</Tabs>

These fire for the logged-in user's own pins — on the acting device as soon as the call succeeds, and over the socket on their other devices — and when a system pin is applied server-side. They never fire because another user pinned their own list. Remove the listener when you are done:

<Tabs>
  <Tab title="Dart">
    ```dart theme={null}
    CometChat.removeConversationListener("listenerId");
    ```
  </Tab>
</Tabs>

<Warning>
  Adding a listener with an ID that is already registered silently replaces the earlier one. Use a unique ID per registration site.
</Warning>

## Pin Limit

<Tabs>
  <Tab title="Dart">
    ```dart theme={null}
    int? limit = CometChat.getPinnedConversationsLimit();
    ```
  </Tab>
</Tabs>

`getPinnedConversationsLimit()` is synchronous and returns `null` when the backend did not serve a limit. System pins do not count against the user's allowance.

## Error Handling

`pinConversation()` and `unpinConversation()` report failures through `onError` with a `CometChatException`. Branch on `e.code` rather than the message text:

| Code | Meaning |
| - | - |
| `ERR_PINNED_CONVERSATIONS_LIMIT_EXCEEDED` | The user's conversation pin cap was reached. The cap is in `errorParams["limit"]`. |
| `ERR_UID_NOT_FOUND` / `ERR_GUID_NOT_FOUND` | The peer named by `conversationWith` does not exist. |
| `ERR_SYSTEM_PINNED_CONVERSATION` | The conversation is **system-pinned**, which a user may never unpin. |

Pins are per-user, so pinning a conversation never affects anyone else's list. Both calls are idempotent: unpinning a conversation that was never pinned succeeds rather than erroring.

<Tabs>
  <Tab title="Dart">
    ```dart theme={null}
    CometChat.pinConversation(conversationWith, conversationType,
        onSuccess: (Conversation conversation) {
      debugPrint("Conversation pinned");
    }, onError: (CometChatException e) {
      if (e.code == 'ERR_PINNED_CONVERSATIONS_LIMIT_EXCEEDED') {
        final limit = e.errorParams?['limit'];
        debugPrint("You can only pin $limit conversations.");
      }
    });
    ```
  </Tab>
</Tabs>

## Feature Availability

<Tabs>
  <Tab title="Dart">
    ```dart theme={null}
    if (CometChat.isPinConversationEnabled()) {
      // Show the Pin Conversation option
    }
    ```
  </Tab>
</Tabs>

`isPinConversationEnabled()` is synchronous and never throws. When neither the login payload nor the app settings carry the flag, it returns `true`, so the feature is not disabled on a backend that predates the flag.

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Retrieve Conversations" icon="comments" href="/docs/sdk/flutter/retrieve-conversations">
    Fetch and order the conversation list
  </Card>

  <Card title="Pin A Message" icon="thumbtack" href="/docs/sdk/flutter/pin-message">
    Highlight a message for everyone in a conversation
  </Card>

  <Card title="Save A Message" icon="bookmark" href="/docs/sdk/flutter/save-message">
    Bookmark a message privately, across conversations
  </Card>

  <Card title="All Real Time Listeners" icon="tower-broadcast" href="/docs/sdk/flutter/real-time-listeners">
    Every listener the SDK exposes, in one place
  </Card>
</CardGroup>


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