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

> **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()`](https://www.cometchat.com/docs/sdk/flutter/setup) and a logged-in user via [`CometChat.login()`](https://www.cometchat.com/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](https://www.cometchat.com/docs/sdk/flutter/retrieve-conversations.md) · [Pin A Message](https://www.cometchat.com/docs/sdk/flutter/pin-message.md) |
> | Full reference | [`Conversation`](https://www.cometchat.com/docs/sdk/reference/entities#conversation) · [`CometChatException`](https://www.cometchat.com/docs/sdk/reference/auxiliary#cometchatexception) |

> [!NOTE]
>
> **Available from Chat SDK v5.0.7.** These APIs require `cometchat_sdk` v5.0.7 or later. See [Setup](https://www.cometchat.com/docs/sdk/flutter/setup.md) to upgrade.

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.

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

**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}");
});
```

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.

## Unpin a Conversation

**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}");
});
```

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](https://www.cometchat.com/docs/sdk/flutter/retrieve-conversations.md) — no extra filter is needed.

**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}");
});
```

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

**Dart**

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

## 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()`.

**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}");
  }
}
```

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:

**Dart**

```dart theme={null}
CometChat.removeConversationListener("listenerId");
```

> [!WARNING]
>
> Adding a listener with an ID that is already registered silently replaces the earlier one. Use a unique ID per registration site.

## Pin Limit

**Dart**

```dart theme={null}
int? limit = CometChat.getPinnedConversationsLimit();
```

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

**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.");
  }
});
```

## Feature Availability

**Dart**

```dart theme={null}
if (CometChat.isPinConversationEnabled()) {
  // Show the Pin Conversation option
}
```

`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

> **[Retrieve Conversations](https://www.cometchat.com/docs/sdk/flutter/retrieve-conversations.md)**
>
> Fetch and order the conversation list

> **[Pin A Message](https://www.cometchat.com/docs/sdk/flutter/pin-message.md)**
>
> Highlight a message for everyone in a conversation

> **[Save A Message](https://www.cometchat.com/docs/sdk/flutter/save-message.md)**
>
> Bookmark a message privately, across conversations

> **[All Real Time Listeners](https://www.cometchat.com/docs/sdk/flutter/real-time-listeners.md)**
>
> Every listener the SDK exposes, in one place


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