> ## 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 and fetch the pinned conversation list with the CometChat React Native SDK.

<Accordion title="AI Integration Quick Reference">
  ```javascript theme={null}
  // Pin / unpin — addressed by peer + type, not by conversationId
  await CometChat.pinConversation("UID", CometChat.RECEIVER_TYPE.USER);
  await CometChat.unpinConversation("GUID", CometChat.RECEIVER_TYPE.GROUP);

  // The DEFAULT list is already pin-ordered — no filter needed.
  // To narrow to pins only, pass the raw string tokens:
  const request = new CometChat.ConversationsRequestBuilder()
    .setPinnedBy(["system", "me"])
    .setLimit(30)
    .build();

  // Read pin state — there is no isPinned()
  const isPinned = conversation.getPinnedAt() !== undefined;
  const isSystemPinned = conversation.getPinnedBy() === "app_system";

  // Cap and availability (off by default — must be mapped per app)
  const limit = await CometChat.getPinConversationLimit();
  const enabled = await CometChat.isPinConversationEnabled();
  ```
</Accordion>

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.

<Warning>
  Pin Conversation requires the `features.ux.conversations.pinned.enabled` flag,
  which is **not seeded in any plan** and must be mapped per app. Until it is,
  every call rejects with `ERR_FEATURE_NOT_ACCESSIBLE`. Gate your UI on
  `isPinConversationEnabled()` before showing the control.
</Warning>

<Note>
  This is separate from an **admin-global pin**, which is managed from the
  [CometChat Dashboard](https://app.cometchat.com) and shows for every user.
  Those 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="TypeScript (User)">
    ```typescript theme={null}
    CometChat.pinConversation(
      "cometchat-uid-1",
      CometChat.RECEIVER_TYPE.USER
    ).then(
      (conversation: CometChat.Conversation) => {
        console.log("Conversation pinned:", conversation);
      },
      (error: CometChat.CometChatException) => {
        console.log("Failed to pin conversation:", error);
      }
    );
    ```
  </Tab>

  <Tab title="JavaScript (User)">
    ```javascript theme={null}
    CometChat.pinConversation(
      "cometchat-uid-1",
      CometChat.RECEIVER_TYPE.USER
    ).then(
      (conversation) => {
        console.log("Conversation pinned:", conversation);
      },
      (error) => {
        console.log("Failed to pin conversation:", error);
      }
    );
    ```
  </Tab>

  <Tab title="TypeScript (Group)">
    ```typescript theme={null}
    CometChat.pinConversation(
      "cometchat-guid-1",
      CometChat.RECEIVER_TYPE.GROUP
    ).then(
      (conversation: CometChat.Conversation) => {
        console.log("Conversation pinned:", conversation);
      },
      (error: CometChat.CometChatException) => {
        console.log("Failed to pin conversation:", error);
      }
    );
    ```
  </Tab>

  <Tab title="JavaScript (Group)">
    ```javascript theme={null}
    CometChat.pinConversation(
      "cometchat-guid-1",
      CometChat.RECEIVER_TYPE.GROUP
    ).then(
      (conversation) => {
        console.log("Conversation pinned:", conversation);
      },
      (error) => {
        console.log("Failed to pin conversation:", error);
      }
    );
    ```
  </Tab>
</Tabs>

It resolves with the full updated `Conversation`, with `pinnedAt` and `pinnedBy` set. Pinning is idempotent.

<Warning>
  A conversation that has **never been messaged**, or that is hidden, cannot be
  newly pinned — the call rejects with `ERR_CONVERSATION_NOT_ACCESSIBLE`. Pin
  from a conversation that already exists in the list.
</Warning>

## Unpin a Conversation

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    CometChat.unpinConversation(
      "cometchat-uid-1",
      CometChat.RECEIVER_TYPE.USER
    ).then(
      (conversation: CometChat.Conversation) => {
        console.log("Conversation unpinned:", conversation);
      },
      (error: CometChat.CometChatException) => {
        console.log("Failed to unpin conversation:", error);
      }
    );
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    CometChat.unpinConversation(
      "cometchat-uid-1",
      CometChat.RECEIVER_TYPE.USER
    ).then(
      (conversation) => {
        console.log("Conversation unpinned:", conversation);
      },
      (error) => {
        console.log("Failed to unpin conversation:", error);
      }
    );
    ```
  </Tab>
</Tabs>

Unpinning returns the conversation to its recency position. A user **cannot** unpin an admin-global pin — hide or disable the unpin control for conversations where `getPinnedBy()` is `"app_system"`.

## Fetch Pinned Conversations

The default conversation list is already **pin-ordered** by the server: admin-global pins first, then the user's own pins, then everything else. Each row already carries `pinnedAt` and `pinnedBy`, so a pinned strip usually needs **no separate call**.

When you do want the pinned set standalone, use `setPinnedBy()`.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    let conversationsRequest: CometChat.ConversationsRequest =
      new CometChat.ConversationsRequestBuilder()
        .setPinnedBy(["system", "me"])
        .setLimit(30)
        .build();

    conversationsRequest.fetchNext().then(
      (conversations: CometChat.Conversation[]) => {
        console.log("Pinned conversations:", conversations);
      },
      (error: CometChat.CometChatException) => {
        console.log("Failed to fetch conversations:", error);
      }
    );
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    let conversationsRequest = new CometChat.ConversationsRequestBuilder()
      .setPinnedBy(["system", "me"])
      .setLimit(30)
      .build();

    conversationsRequest.fetchNext().then(
      (conversations) => {
        console.log("Pinned conversations:", conversations);
      },
      (error) => {
        console.log("Failed to fetch conversations:", error);
      }
    );
    ```
  </Tab>
</Tabs>

| Token      | Meaning                                             |
| ---------- | --------------------------------------------------- |
| `"me"`     | Conversations the logged-in user pinned themselves. |
| `"system"` | Conversations pinned globally by an admin.          |

<Warning>
  The match is **exact and case-sensitive**, and an unrecognised value is dropped
  silently rather than raising. `setPinnedBy(["Me"])` leaves nothing behind, the
  `pinnedBy` parameter is omitted entirely, and you get the **full conversation
  list** back — not an error, and not a pinned-only list. Passing an empty array
  does the same thing. Use the lowercase tokens exactly as written above.
</Warning>

<Note>
  These are plain strings in the React Native SDK — there is no
  `CometChat.PINNED_BY` constant to import. The server rejects any other value
  with `ERR_BAD_REQUEST` — end users cannot query another user's pins — so the
  builder drops unrecognised entries before the request is sent.
</Note>

## Check if a Conversation is Pinned

As with messages, the React Native SDK has **no `isPinned()` helper** — the presence of `pinnedAt` *is* the boolean.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    const isPinned: boolean = conversation.getPinnedAt() !== undefined;

    if (isPinned) {
      console.log("Pinned at:", conversation.getPinnedAt());
      console.log("Pinned by:", conversation.getPinnedBy());

      // A user may not unpin an admin/global pin
      const isSystemPinned: boolean = conversation.getPinnedBy() === "app_system";
    }
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    const isPinned = conversation.getPinnedAt() !== undefined;

    if (isPinned) {
      console.log("Pinned at:", conversation.getPinnedAt());
      console.log("Pinned by:", conversation.getPinnedBy());

      // A user may not unpin an admin/global pin
      const isSystemPinned = conversation.getPinnedBy() === "app_system";
    }
    ```
  </Tab>
</Tabs>

| Method          | Returns                                                                      |
| --------------- | ---------------------------------------------------------------------------- |
| `getPinnedAt()` | The pin timestamp, or `undefined` when the conversation is not pinned.       |
| `getPinnedBy()` | The pinner's UID, or `"app_system"` for an admin/global pin, or `undefined`. |

## Keeping the List in Sync

<Warning>
  The React Native SDK does **not** expose a `ConversationListener`, and there
  are no `onConversationPinned` / `onConversationUnpinned` callbacks. Conversation
  pins made on another device do not arrive over the socket.
</Warning>

Update your list from the promise on the device that performed the action, and re-fetch the conversation list when the screen regains focus to pick up pins made elsewhere.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    const conversation: CometChat.Conversation = await CometChat.pinConversation(
      "cometchat-uid-1",
      CometChat.RECEIVER_TYPE.USER
    );

    // The server accepted it — move the row yourself.
    setConversations((prev) => reorderWithPinnedFirst(prev, conversation));
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    CometChat.pinConversation("cometchat-uid-1", CometChat.RECEIVER_TYPE.USER).then(
      (conversation) => {
        // The server accepted it — move the row yourself.
        setConversations((prev) => reorderWithPinnedFirst(prev, conversation));
      }
    );
    ```
  </Tab>
</Tabs>

## Pin Limit

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

  <Tab title="JavaScript">
    ```javascript theme={null}
    CometChat.getPinConversationLimit().then((limit) => {
      console.log("Pinned conversations limit:", limit);
    });
    ```
  </Tab>
</Tabs>

Both resolve to `null` when the app settings carry no value. `getSystemPinConversationLimit()` is the separate admin/global cap, enforced independently — system pins do not consume a user's allowance.

<Note>
  The conversation pin cap and the message pin cap are **separate quotas with
  different values**, and both are server-owned and tenant-overridable. Read
  each one at runtime rather than assuming a number or reusing the other.
</Note>

## Feature Availability

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

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

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

Unlike the two message-level flags, this one is **not seeded anywhere and is off by default** — treat a `false` as the normal case for a new app, not as an error.

***

## Next Steps

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

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

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

  <Card title="Delete A Conversation" icon="trash" href="/docs/sdk/react-native/delete-conversation">
    Remove a conversation from the list
  </Card>
</CardGroup>
