> ## Documentation Index
> Fetch the complete documentation index at: https://cometchat-22654f5b-docs-subscribed-threads-quoted-reply-pre.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Pinned Messages

> Display the messages pinned in a conversation, with unpin, save, copy, share and info actions.

<Accordion title="AI Integration Quick Reference">
  ```json theme={null}
  {
    "component": "CometChatPinnedMessages",
    "package": "@cometchat/chat-uikit-react-native",
    "import": "import { CometChatPinnedMessages } from \"@cometchat/chat-uikit-react-native\";",
    "description": "Lists the messages pinned in a conversation, newest pin first. A pin is conversation-wide and visible to everyone.",
    "requires": {
      "dashboardFlag": "Pin Messages must be enabled for your app in the CometChat Dashboard",
      "resolution": "The kit reads the flag at login and on every reconnect — no app code required. Force a re-read with refreshPinSaveFeatures()"
    },
    "props": {
      "data": {
        "user": { "type": "CometChat.User", "default": "undefined", "note": "Scopes the panel to a one-on-one conversation. Mutually exclusive with group" },
        "group": { "type": "CometChat.Group", "default": "undefined", "note": "Scopes the panel to a group. Mutually exclusive with user" },
        "limit": { "type": "number", "default": 30, "note": "Page size for the fetch; the SDK caps it at 100" }
      },
      "callbacks": {
        "onBack": "() => void",
        "onItemPress": "(message: CometChat.BaseMessage) => void"
      },
      "visibility": {
        "hideUnpinMessageOption": { "type": "boolean", "default": false },
        "hideSaveMessageOption": { "type": "boolean", "default": false },
        "hideUnsaveMessageOption": { "type": "boolean", "default": false },
        "hideCopyMessageOption": { "type": "boolean", "default": false },
        "hideShareMessageOption": { "type": "boolean", "default": false },
        "hideMessageInfoOption": { "type": "boolean", "default": false }
      },
      "customization": {
        "ItemView": "(message: CometChat.BaseMessage) => JSX.Element",
        "title": { "type": "string", "default": "localized" },
        "style": "DeepPartial<PinnedMessagesStyle>"
      }
    },
    "helpers": {
      "isPinned": "(message) => boolean",
      "isSystemPin": "(message) => boolean — pinnedBy is SYSTEM_PINNER (\"app_system\")"
    }
  }
  ```
</Accordion>

`CometChatPinnedMessages` lists the messages pinned in one conversation, newest pin first. A pin is
conversation-wide and visible to everyone, so this panel shows the same set to every participant.

<Warning>
  **Pin Messages must be enabled for your app in the CometChat Dashboard.** Until it is, the pin
  options never render and this panel has nothing to show. The UI Kit reads that flag itself at
  login and on every reconnect — there is no app code to write.
</Warning>

## Where It Fits

Open it from the message header or an overflow menu, scoped to the conversation the user is in. It is
a full-screen panel with its own back affordance rather than an inline strip.

## Minimal Render

Pass **exactly one** of `user` or `group` — the panel is scoped to a single conversation.

<Tabs>
  <Tab title="TypeScript (User)">
    ```tsx theme={null}
    import { CometChatPinnedMessages } from "@cometchat/chat-uikit-react-native";

    <CometChatPinnedMessages
      user={user}
      onBack={() => navigation.goBack()}
      onItemPress={(message: CometChat.BaseMessage) => scrollToMessage(message)}
    />
    ```
  </Tab>

  <Tab title="TypeScript (Group)">
    ```tsx theme={null}
    import { CometChatPinnedMessages } from "@cometchat/chat-uikit-react-native";

    <CometChatPinnedMessages
      group={group}
      onBack={() => navigation.goBack()}
      onItemPress={(message: CometChat.BaseMessage) => scrollToMessage(message)}
    />
    ```
  </Tab>
</Tabs>

## Enabling the feature

Pin and Save are gated on your app's Dashboard settings. The UI Kit resolves those flags at login
and on every reconnect and switches the options on or off accordingly, so there is nothing to call
at start-up.

`PinSaveConfig` is exported for the cases where you need to read or override that resolved state —
not as a switch you must throw.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    import {
      PinSaveConfig,
      refreshPinSaveFeatures,
      getPinSaveFeatures,
    } from "@cometchat/chat-uikit-react-native";

    // Read what the kit resolved from the Dashboard flags
    const features = getPinSaveFeatures();

    // Force a re-read after flipping a flag in the dashboard, without a re-login
    await refreshPinSaveFeatures();
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    import {
      PinSaveConfig,
      refreshPinSaveFeatures,
      getPinSaveFeatures,
    } from "@cometchat/chat-uikit-react-native";

    const features = getPinSaveFeatures();
    refreshPinSaveFeatures();
    ```
  </Tab>
</Tabs>

<Note>
  You do not need to fetch these flags yourself, and you do not need to enable the feature in code.
  The kit resolves them at login and after every reconnect. `refreshPinSaveFeatures()` exists for the
  case where an admin flips a flag in the Dashboard while the app is running.
</Note>

## Props

| Property                  | Type                               | Default   | Description                                                                     |
| ------------------------- | ---------------------------------- | --------- | ------------------------------------------------------------------------------- |
| `user`                    | `CometChat.User`                   | —         | Scopes the panel to a one-on-one conversation. Mutually exclusive with `group`. |
| `group`                   | `CometChat.Group`                  | —         | Scopes the panel to a group. Mutually exclusive with `user`.                    |
| `limit`                   | `number`                           | `30`      | Page size for the fetch. The SDK rejects a value above 100.                     |
| `onBack`                  | `() => void`                       | —         | Called when the back affordance is pressed.                                     |
| `onItemPress`             | `(message) => void`                | —         | Called when a row is pressed — use it to jump to the message in the list.       |
| `hideUnpinMessageOption`  | `boolean`                          | `false`   | Hides Unpin in the row menu.                                                    |
| `hideSaveMessageOption`   | `boolean`                          | `false`   | Hides Save in the row menu.                                                     |
| `hideUnsaveMessageOption` | `boolean`                          | `false`   | Hides Unsave in the row menu.                                                   |
| `hideCopyMessageOption`   | `boolean`                          | `false`   | Hides Copy.                                                                     |
| `hideShareMessageOption`  | `boolean`                          | `false`   | Hides Share.                                                                    |
| `hideMessageInfoOption`   | `boolean`                          | `false`   | Hides Message Info.                                                             |
| `ItemView`                | `(message) => JSX.Element`         | —         | Replaces the default row entirely.                                              |
| `title`                   | `string`                           | localized | Panel title.                                                                    |
| `style`                   | `DeepPartial<PinnedMessagesStyle>` | —         | Style overrides.                                                                |

## Reading pin state yourself

The kit exports helpers so a custom `ItemView` does not have to reimplement them. The SDK has no
`isPinned()` of its own — these are UI Kit conveniences over `getPinnedAt()` and `getPinnedBy()`.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    import { isPinned, isSystemPin, SYSTEM_PINNER } from "@cometchat/chat-uikit-react-native";

    if (isPinned(message)) {
      // Pinned by somebody in this conversation
    }

    if (isSystemPin(message)) {
      // An admin/global pin — pinnedBy is SYSTEM_PINNER ("app_system"), not a real member
    }
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    import { isPinned, isSystemPin } from "@cometchat/chat-uikit-react-native";

    if (isPinned(message)) {
      // Pinned by somebody in this conversation
    }

    if (isSystemPin(message)) {
      // An admin/global pin
    }
    ```
  </Tab>
</Tabs>

## Actions and Events

The panel keeps itself current from the kit's event bus, so a pin made anywhere — this screen, the
message list, or another participant's device — lands here without a manual refresh.

| Event                                   | Fires when                                                      |
| --------------------------------------- | --------------------------------------------------------------- |
| `ccMessagePinned` / `ccMessageUnpinned` | This device pinned or unpinned.                                 |
| `ccMessageSaved` / `ccMessageUnsaved`   | This device saved or unsaved.                                   |
| `onMessagePinned` / `onMessageUnpinned` | Another participant pinned or unpinned — broadcast to everyone. |
| `onMessageSaved` / `onMessageUnsaved`   | The same user saved on another device — private multi-device.   |

<Note>
  **Removing asks, adding does not.** Unpin and Unsave show a confirmation; Pin and Save run
  immediately. This is deliberate and consistent across all CometChat UI Kits — the additive action
  has its own undo one tap away, the destructive one does not.
</Note>

## Common Patterns

### Jump to the pinned message

<Tabs>
  <Tab title="TypeScript">
    ```tsx theme={null}
    <CometChatPinnedMessages
      group={group}
      onItemPress={(message: CometChat.BaseMessage) => {
        navigation.goBack();
        scrollToMessageId(message.getId());
      }}
    />
    ```
  </Tab>

  <Tab title="JavaScript">
    ```jsx theme={null}
    <CometChatPinnedMessages
      group={group}
      onItemPress={(message) => {
        navigation.goBack();
        scrollToMessageId(message.getId());
      }}
    />
    ```
  </Tab>
</Tabs>

### Read-only panel

<Tabs>
  <Tab title="TypeScript">
    ```tsx theme={null}
    <CometChatPinnedMessages
      group={group}
      hideUnpinMessageOption
      hideSaveMessageOption
      hideUnsaveMessageOption
    />
    ```
  </Tab>

  <Tab title="JavaScript">
    ```jsx theme={null}
    <CometChatPinnedMessages
      group={group}
      hideUnpinMessageOption
      hideSaveMessageOption
      hideUnsaveMessageOption
    />
    ```
  </Tab>
</Tabs>

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Saved Messages" icon="bookmark" href="/ui-kit/react-native/saved-messages">
    The private, cross-conversation counterpart
  </Card>

  <Card title="Message List" icon="comments" href="/ui-kit/react-native/message-list">
    Where the Pin and Save options are raised
  </Card>

  <Card title="Pin A Message (SDK)" icon="thumbtack" href="/sdk/react-native/pin-message">
    The SDK methods underneath this component
  </Card>

  <Card title="Events" icon="tower-broadcast" href="/ui-kit/react-native/events">
    Every UI Kit event, in one place
  </Card>
</CardGroup>
