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

# Pin & Save Messages

> Pin messages for everyone in a conversation and save messages privately using the CometChat iOS SDK, including message fields, fetching, and listener events.

<Accordion title="AI Integration Quick Reference">
  ```swift theme={null}
  // Pin a message for everyone in the conversation
  CometChat.pinMessage(messageId: 148) { msg in } onError: { err in }
  CometChat.unpinMessage(messageId: 148) { msg in } onError: { err in }

  // Save a message privately for the logged-in user
  CometChat.saveMessage(messageId: 148) { msg in } onError: { err in }
  CometChat.unsaveMessage(messageId: 148) { msg in } onError: { err in }

  // Presence of the timestamp IS the boolean
  let isPinned = message.pinnedAt != 0
  let isSaved  = message.savedAt != 0

  // Fetch pinned messages in a conversation
  MessagesRequest.MessageRequestBuilder().set(guid: "guid").set(pinned: true).set(limit: 100).build()

  // Fetch the user's saved messages across all conversations
  MessagesRequest.MessageRequestBuilder().set(saved: true).set(limit: 100).build()

  // Listen for events (CometChatMessageDelegate)
  func onMessagePinned(message: BaseMessage) { }
  func onMessageUnpinned(message: BaseMessage) { }
  func onMessageSaved(message: BaseMessage) { }
  func onMessageUnsaved(message: BaseMessage) { }
  ```
</Accordion>

Pin and save let users mark messages for later. The two features look similar but differ in who sees the result:

|            | Pin                             | Save                |
| ---------- | ------------------------------- | ------------------- |
| Visibility | Everyone in the conversation    | Private to the user |
| Scope      | One conversation                | All conversations   |
| Permission | Group owner, admin or moderator | Anyone              |
| Limit      | 100 per conversation            | 100 per user        |

***

## Pin a Message

Pinning is conversation-wide: every participant sees the message as pinned.

<Tabs>
  <Tab title="Swift">
    ```swift theme={null}
    CometChat.pinMessage(messageId: 148) { message in
        print("Pinned at: \(message.pinnedAt) by \(message.pinnedBy)")
    } onError: { error in
        print("Error: \(error.errorDescription)")
    }
    ```
  </Tab>
</Tabs>

<Note>
  Pinning is permission-gated. In a group, only the owner, admins and moderators can pin or unpin; everyone can view the pinned list. In one-to-one conversations both participants can pin.
</Note>

***

## Unpin a Message

<Tabs>
  <Tab title="Swift">
    ```swift theme={null}
    CometChat.unpinMessage(messageId: 148) { message in
        // pinnedAt is cleared back to 0
        print("Unpinned: \(message.pinnedAt == 0)")
    } onError: { error in
        print("Error: \(error.errorDescription)")
    }
    ```
  </Tab>
</Tabs>

***

## Save a Message

Saving is private to the logged-in user and syncs across that user's own devices. It carries no permission checks.

<Tabs>
  <Tab title="Swift">
    ```swift theme={null}
    CometChat.saveMessage(messageId: 148) { message in
        print("Saved at: \(message.savedAt)")
    } onError: { error in
        print("Error: \(error.errorDescription)")
    }
    ```
  </Tab>
</Tabs>

***

## Unsave a Message

<Tabs>
  <Tab title="Swift">
    ```swift theme={null}
    CometChat.unsaveMessage(messageId: 148) { message in
        print("Unsaved: \(message.savedAt == 0)")
    } onError: { error in
        print("Error: \(error.errorDescription)")
    }
    ```
  </Tab>
</Tabs>

All four methods return the updated [`BaseMessage`](/sdk/reference/messages#basemessage).

***

## Message Fields

Three fields on `BaseMessage` carry the state:

| Field      | Type     | Scope      | Description                                                    |
| ---------- | -------- | ---------- | -------------------------------------------------------------- |
| `pinnedAt` | `Double` | Global     | When the message was pinned. `0` when not pinned.              |
| `pinnedBy` | `String` | Global     | UID of the last user to pin it. `app_system` for a system pin. |
| `savedAt`  | `Double` | Per-viewer | When the logged-in user saved it. `0` when not saved.          |

<Warning>
  **Presence of the timestamp is the boolean.** There is no separate `isPinned` flag — check `pinnedAt != 0` and `savedAt != 0`. Unpinning and unsaving clear the field back to `0` rather than leaving a stale value.

  `savedAt` is per-viewer: it is only ever populated for the user who saved the message, so you cannot use it to tell whether someone else saved it.
</Warning>

<Tabs>
  <Tab title="Swift">
    ```swift theme={null}
    let isPinned = message.pinnedAt != 0
    let isSaved  = message.savedAt != 0

    // A pin applied by an admin from the dashboard rather than a user
    let isSystemPin = message.pinnedBy == "app_system"
    ```
  </Tab>
</Tabs>

***

## Fetch Pinned Messages

Pinned messages are fetched with `MessagesRequest` filtered by `pinned`, scoped to a conversation with `uid` or `guid`.

<Tabs>
  <Tab title="Group">
    ```swift theme={null}
    let request = MessagesRequest.MessageRequestBuilder()
        .set(guid: "cometchat-guid-1")
        .set(pinned: true)
        .set(limit: 100)
        .build()

    request.fetchPrevious { messages in
        print("Pinned: \(messages?.count ?? 0)")
    } onError: { error in
        print("Error: \(error?.errorDescription)")
    }
    ```
  </Tab>

  <Tab title="One-to-one">
    ```swift theme={null}
    let request = MessagesRequest.MessageRequestBuilder()
        .set(uid: "cometchat-uid-1")
        .set(pinned: true)
        .set(limit: 100)
        .build()

    request.fetchPrevious { messages in
        print("Pinned: \(messages?.count ?? 0)")
    } onError: { error in
        print("Error: \(error?.errorDescription)")
    }
    ```
  </Tab>
</Tabs>

***

## Fetch Saved Messages

Saved messages are a per-user collection spanning every conversation, so the request takes **no** `uid` or `guid`.

<Tabs>
  <Tab title="Swift">
    ```swift theme={null}
    let request = MessagesRequest.MessageRequestBuilder()
        .set(saved: true)
        .set(limit: 100)
        .build()

    request.fetchPrevious { messages in
        // Each message carries receiverUid / receiverType identifying its source conversation
        print("Saved: \(messages?.count ?? 0)")
    } onError: { error in
        print("Error: \(error?.errorDescription)")
    }
    ```
  </Tab>
</Tabs>

<Note>
  Both limits are capped at 100 server-side, so a single page fetches the whole list.
</Note>

***

## Real-time Events

Implement `CometChatMessageDelegate` to receive pin and save updates. All four callbacks are optional and each carries the full updated message.

<Tabs>
  <Tab title="Swift">
    ```swift theme={null}
    extension ViewController: CometChatMessageDelegate {

        func onMessagePinned(message: BaseMessage) {
            // Broadcast to everyone in the conversation
            print("Pinned by \(message.pinnedBy)")
        }

        func onMessageUnpinned(message: BaseMessage) {
            print("Unpinned: \(message.id)")
        }

        func onMessageSaved(message: BaseMessage) {
            // Private — only reaches the saving user's other devices
            print("Saved: \(message.id)")
        }

        func onMessageUnsaved(message: BaseMessage) {
            print("Unsaved: \(message.id)")
        }
    }
    ```
  </Tab>
</Tabs>

Register the delegate as you would for any message event — see [Real-time Delegates and Listeners](/sdk/ios/all-real-time-delegates-listeners).

Pin events are broadcast to every participant. Save events are private and reach only the saving user's other devices.

***

## Feature Availability

Both features are gated per app and can be checked before showing any UI:

<Tabs>
  <Tab title="Swift">
    ```swift theme={null}
    if CometChat.isPinMessageEnabled() {
        // Show pin options
    }

    if CometChat.isSaveMessageEnabled() {
        // Show save options
    }
    ```
  </Tab>
</Tabs>

***

## Error Handling

| Error code                           | Meaning                                                   |
| ------------------------------------ | --------------------------------------------------------- |
| `ERR_PINNED_MESSAGES_LIMIT_EXCEEDED` | The conversation already has the maximum pinned messages. |
| `ERR_SAVED_MESSAGES_LIMIT_EXCEEDED`  | The user already has the maximum saved messages.          |
| `ERR_PERMISSION_DENIED`              | The user's scope does not allow pinning here.             |
| `ERR_MESSAGE_NO_ACCESS`              | The user cannot access this message.                      |
| `ERR_MESSAGE_ACTION_NOT_ALLOWED`     | The action is not allowed on this message.                |
| `ERR_FEATURE_NOT_ACCESSIBLE`         | The feature is not enabled for this app.                  |

For the two limit errors, read the actual cap from the exception's `errorParams` rather than hard-coding a number — the limit is server-owned and may change.

***

## Edge Cases

| Scenario                             | Behavior                                                                     |
| ------------------------------------ | ---------------------------------------------------------------------------- |
| A pinned message is deleted          | Automatically unpinned; no placeholder is left behind.                       |
| A pinned message is edited           | Keeps its pin.                                                               |
| A saved message becomes inaccessible | Dropped from subsequent saved fetches.                                       |
| A thread reply                       | Can be pinned and saved. Pinned lists return the parent message for context. |
| Someone else unpins your pin         | Allowed — any user with permission can unpin.                                |

***

## Related

* [Pinned Messages UI Component](/ui-kit/ios/pinned-messages)
* [Saved Messages UI Component](/ui-kit/ios/saved-messages)
* [Additional Message Filtering](/sdk/ios/additional-message-filtering)
* [Real-time Delegates and Listeners](/sdk/ios/all-real-time-delegates-listeners)
