# Pinned messages

Pinned messages allow users to highlight important messages, make announcements, or temporarily promote content. Pinning a message is, by default, restricted to certain user roles, but this is flexible. Each channel can have multiple pinned messages and these can be created or updated with or without an expiration.

### Pin and unpin a message

An existing message can be updated to be pinned or unpinned by using the `.pinMessage` and `.unpinMessage` methods. Or a new message can be pinned when it is sent by setting the `pinned` and `pin_expires` fields when using `channel.sendMessage` .

<codetabs>

<codetabs-item value="kotlin" label="Kotlin">

```kotlin
// Create pinned message
val pinExpirationDate = Calendar.getInstance().apply { set(2077, 1, 1) }.time
val message = Message(
  text = "Hey punk",
  pinned = true,
  pinExpires = pinExpirationDate
)

channel.sendMessage(message).enqueue { /* ... */ }

// Unpin message
channel.unpinMessage(message).enqueue { /* ... */ }

// Pin message for 120 seconds
channel.pinMessage(message, timeout = 120).enqueue { /* ... */ }

// Change message expiration to 2077
channel.pinMessage(message, expirationDate = pinExpirationDate).enqueue { /* ... */ }

// Remove expiration date from pinned message
channel.pinMessage(message, expirationDate = null).enqueue { /* ... */ }
```

</codetabs-item>

<codetabs-item value="javascript" label="JavaScript">

```js
// create pinned message
const { message } = await channel.sendMessage({
  text: "my message",
  pinned: true,
  pin_expires: "2077-01-01T00:00:00Z",
});

// unpin message
await client.unpinMessage(message);

// pin message for 120 seconds
await client.pinMessage(message, 120);

// change message expiration to 2077
await client.pinMessage(message, "2077-01-01T00:00:00Z");

// remove expiration date from pinned message
await client.pinMessage(message, null);
```

</codetabs-item>

<codetabs-item value="dart" label="Dart">

```dart
// create pinned message
final message = await channel
  .sendMessage(Message(
   text: 'my message',
   pinned: true,
   pinExpires: DateTime.now().add(Duration(days: 3)),
  ))
  .then((resp) => resp.message);

// unpin message
await channel.unpinMessage(message);

// pin message for 120 seconds
await channel.pinMessage(message, 120);

// change message expiration to 2077
await channel.pinMessage(message, DateTime(2077));

// remove expiration date from pinned message
await channel.pinMessage(message, null);
```

</codetabs-item>

<codetabs-item value="swift" label="Swift">

```swift
let messageController = client.messageController(cid: .init(type: .messaging, id: "general"), messageId: "message-id")

// Pin message for 120 seconds
messageController.pin(.expirationTime(120))

// Unpin message
messageController.unpin()

// Pin message to specific date
messageController.pin(.expirationDate(Date()))
```

</codetabs-item>

<codetabs-item value="unreal" label="Unreal">

```cpp
// Not yet supported in the Unreal SDK
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
$response = $channel->sendMessage([
	'text' => 'Hey punk',
 'pinned' => true,
 'pin_expires' => "2077-01-01T00:00:00Z",
	],
	$user["id"]);

// unpin message
 $client->unPinMessage($response["message"]["id"], $user["id"]);

// pin message for 120 seconds
$client->pinMessage($response["message"]["id"], $user["id"], 120);
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
response = channel.send_message({"pinned": True, "text": "hello world"}, user["id"])

# unpin message:
client.unpin_message(response["message"]["id"], user["id"])

# pin message for 120 secs:
client.pin_message(response["message"]["id"], user["id"], 120)
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
# require 'stream-chat'

response = channel.send_message({pinned: true, text: "hello world"}, user["id"])

# unpin message:
client.unpin_message(response["message"]["id"], user["id"])

# pin message for 120 secs:
client.pin_message(response["message"]["id"], user["id"], 120)
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
msg := &Message{Text: "Hey punk", Pinned: true}
messageResp, err := channel.SendMessage(ctx, msg, user.ID)

// pin message
client.UnPinMessage(ctx, msg.ID, user.ID)

// pin message for 120 secs:
exp := time.Now().Add(time.Second * 120)
client.PinMessage(context.Background(), messageResp.Message.ID, user.ID, &exp)
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
var msg = new MessageRequest { Text = "hey punk", Pinned = true };
var response = await messageClient.SendMessageAsync(channel.Type, channel.Id, msg, user.Id);

// unpin message
await messageClient.UnpinMessageAsync(response.Message.Id, user.Id);

// pin message for 120 sec:
await messageClient.PinMessageAsync(response.Message.Id, user.Id, TimeSpan.FromSeconds(120));
```

</codetabs-item>

<codetabs-item value="unity" label="Unity">

```csharp
// Pin until unpinned
await message.PinAsync();

// Pin for 7 days
await message.PinAsync(new DateTime().AddDays(7));

// Unpin previously pinned message
await message.UnpinAsync();
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
// Android SDK

// Create pinned message
Calendar calendar = Calendar.getInstance();
calendar.set(2077, 1, 1);
Date pinExpirationDate = calendar.getTime();

Message message = new Message();
message.setText("Hey punk");
message.setPinned(true);
message.setPinExpires(pinExpirationDate);

channelClient.sendMessage(message).enqueue(result -> { /* ... */ });

// Unpin message
channelClient.unpinMessage(message).enqueue(result -> { /* ... */ });

// Pin message for 120 seconds
channelClient.pinMessage(message, 120).enqueue(result -> { /* ... */ });

// Change message expiration to 2077
channelClient.pinMessage(message, pinExpirationDate).enqueue(result -> { /* ... */ });

// Remove expiration date from pinned message
channelClient.pinMessage(message, null).enqueue(result -> { /* ... */ });

// Backend SDK

// create pinned message
Message message =
  Message.send(channelType, channelId)
    .message(
      MessageRequestObject.builder()
        .text("my message")
        .pinned(true)
        .pinExpires(
          new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
            .parse("2077-01-01T00:00:00Z"))
        .userId(userId)
        .build())
    .request()
    .getMessage();

// unpin message
MessageRequestObject messageRequestObject = MessageRequestObject.buildFrom(message);
messageRequestObject.setPinned(false);
Message message2 =
  Message.update(message.getId()).message(messageRequestObject).request().getMessage();

// pin message for 120 seconds
MessageRequestObject messageRequestObject2 = MessageRequestObject.buildFrom(message2);
messageRequestObject2.setPinned(true);
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.SECOND, 120);
messageRequestObject2.setPinExpires(calendar.getTime());
Message message3 =
  Message.update(message2.getId()).message(messageRequestObject2).request().getMessage();

// change message expiration to 2077
MessageRequestObject messageRequestObject3 = MessageRequestObject.buildFrom(message3);
messageRequestObject3.setPinExpires(
  new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX").parse("2077-01-01T00:00:00Z"));
Message message4 =
  Message.update(message3.getId()).message(messageRequestObject3).request().getMessage();

// remove expiration date from pinned message
MessageRequestObject messageRequestObject4 = MessageRequestObject.buildFrom(message4);
messageRequestObject4.setPinExpires(null);
Message.update(message4.getId()).message(messageRequestObject4).request();
```

</codetabs-item>

</codetabs>

| name        | type    | description                                                                          | default | optional |
| ----------- | ------- | ------------------------------------------------------------------------------------ | ------- | -------- |
| pinned      | boolean | Indicates whether the message is pinned or not                                       | false   | ✓        |
| pinned_at   | string  | Date when the message got pinned                                                     | -       | ✓        |
| pin_expires | string  | Date when the message pin expires. An empty value means that message does not expire | null    | ✓        |
| pinned_by   | User    | The user that pinned the message                                                     | -       | ✓        |

<admonition type="info">

To pin the message user has to have `PinMessage` permission. You can find the list of permissions and defaults in [Permission Resources](/chat/docs/<framework>/permissions_reference/) and [Default Permissions](/chat/docs/<framework>/chat_permission_policies/) sections

</admonition>

### Retrieve pinned messages

You can easily retrieve the last 10 pinned messages from the `channel.pinned_messages` field:

<codetabs>

<codetabs-item value="kotlin" label="Kotlin">

```kotlin
channelClient.query(QueryChannelRequest()).enqueue { result ->
  if (result is Result.Success) {
    val pinnedMessages: List<Message> = result.value.pinnedMessages
  } else {
    // Handle Result.Failure
  }
}
```

</codetabs-item>

<codetabs-item value="javascript" label="JavaScript">

```js
// get channel state
const channelState = await client.channel("messaging", channelId).query();
// get pinned messages from it
const pinnedMessages = channelState.pinned_messages;
```

</codetabs-item>

<codetabs-item value="dart" label="Dart">

```dart
// get channel state
final channelState = await channel.query();

// get pinned messages from it
final pinnedMessages = channelState.pinnedMessages;
```

</codetabs-item>

<codetabs-item value="swift" label="Swift">

```swift
let channelController = client.channelController(for: .init(type: .messaging, id: "general"))

channelController.synchronize() { error in
  if error == nil {
    channelController.channel?.pinnedMessages
  }
}
```

</codetabs-item>

<codetabs-item value="unreal" label="Unreal">

```cpp
// Not yet supported in the Unreal SDK
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
$response = $channel->query([
      "watch" => false,
      "state" => false,
      "presence" => false
    ]);
$pinnedMessages = $response["pinned_messages"]
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
channel_state = channel.query(watch=False, state=False, presence=False)

pinned_message = channel_state["pinned_messages"]
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
channel_state = channel.query(watch: false, state: false, presence: false)

pinned_message = channel_state['pinned_messages']
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
_, err = channel.Query(ctx, map[string]interface{}{"watch": false, "state": true})
msgs := channel.PinnedMessages
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
var currentChannelState = await _channelClient.GetOrCreateAsync(_channel.Type, _channel.Id, new ChannelGetRequest
{
  State = true,
  Watch = false,
});

var pinnedMessags = currentChannelState.PinnedMessages;
```

</codetabs-item>

<codetabs-item value="unity" label="Unity">

```csharp
// Will be implemented soon, raise a GitHub issue if you need this feature https://github.com/GetStream/stream-chat-unity/issues/
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
// Android SDK
channelClient.query(new QueryChannelRequest()).enqueue(result -> {
  if (result.isSuccess()) {
    List<Message> pinnedMessages = result.data().getPinnedMessages();
  } else {
    // Handle result.error()
  }
});

// Backend SDK
ChannelGetResponse resp = Channel.getOrCreate("type", "id").request();
List<Message> messages = resp.getPinnedMessages();
```

</codetabs-item>

</codetabs>

<admonition type="info">

To learn more about channels you can visit [Querying Channels](/chat/docs/<framework>/query_channels/) page

</admonition>

### Paginate over all pinned messages

Stream Chat also provides dedicated endpoint for pinned messages of the channel which can be used to retrieve all pinned messages page by page:

<codetabs>

<codetabs-item value="kotlin" label="Kotlin">

```kotlin
// List the first page of pinned messages, pinned before now, of the channel with descending direction (newest on top)
channelClient.getPinnedMessages(
  limit = 10,
  sort = QuerySortByField.descByName("pinnedAt"),
  pagination = PinnedMessagesPagination.BeforeDate(
    date = Date(),
    inclusive = false,
  ),
).enqueue { result ->
  if (result is Result.Success) {
    val pinnedMessages: List<Message> = result.value
  } else {
    // Handle Result.Failure
  }
}

// You can use a pinnedAt date retrieved from the previous request to get the next page
val nextDate = Date()
// List the next page of pinned messages
channelClient.getPinnedMessages(
  limit = 10,
  sort = QuerySortByField.descByName("pinnedAt"),
  pagination = PinnedMessagesPagination.BeforeDate(
    date = nextDate,
    inclusive = false,
  ),
).enqueue { result ->
  if (result is Result.Success) {
    val pinnedMessages: List<Message> = result.value
  } else {
    // Handle Result.Failure
  }
}
```

</codetabs-item>

<codetabs-item value="javascript" label="JavaScript">

```js
// list first page of pinned messages of the channel with descending direction (newest on top)
const descPage1 = await channel.getPinnedMessages(
  { limit: 10 },
  { pinned_at: -1 },
);
const descLastMsg = descPage1.messages[descPage1.messages.length - 1];
// list next page of pinned messages
const descPage2 = await channel.getPinnedMessages(
  {
    limit: 10,
    id_lt: descLastMsg.id,
  },
  { pinned_at: -1 },
);

// list first page of pinned messages of the channel with ascending direction (oldest on top)
const ascPage1 = await channel.getPinnedMessages({ limit: 10 });
const ascLastMsg = ascPage1.messages[ascPage1.messages.length - 1];
// list next page of pinned messages with ascending direction
const ascPage2 = await channel.getPinnedMessages({
  limit: 10,
  id_gt: ascLastMsg.id,
});
```

</codetabs-item>

<codetabs-item value="unreal" label="Unreal">

```cpp
// Not yet supported in the Unreal SDK
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
// not implemented yet
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
# not implemented yet
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
# not implemented yet
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
// not implemented yet
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
// not implemented yet
```

</codetabs-item>

<codetabs-item value="unity" label="Unity">

```csharp
// Will be implemented soon, raise a GitHub issue if you need this feature https://github.com/GetStream/stream-chat-unity/issues/
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
// Android SDK

// List the first page of pinned messages, pinned before now, of the channel with descending direction (newest on top)
channelClient.getPinnedMessages(10, QuerySortByField.descByName("pinnedAt"), new PinnedMessagesPagination.BeforeDate(new Date(), false))
    .enqueue(result -> {
      if (result.isSuccess()) {
        List<Message> pinnedMessages = result.data();
      } else {
        // Handle result.error()
      }
    });

// You can use a pinnedAt date retrieved from the previous request to get the next page
Date nextDate = new Date();
// List the next page of pinned messages
channelClient.getPinnedMessages(10, QuerySortByField.descByName("pinnedAt"), new PinnedMessagesPagination.BeforeDate(nextDate, false))
    .enqueue(result -> {
      if (result.isSuccess()) {
        List<Message> pinnedMessages = result.data();
      } else {
        // Handle result.error()
      }
    });


// Backend SDK
Message.search()
  .filterCondition("pinned", true) // Additional filters can also be applied
  .request();
```

</codetabs-item>

<codetabs-item value="swift" label="Swift">

```swift
channelController.loadPinnedMessages(
  pageSize: 10,
  sorting: [.init(key: .pinnedAt, isAscending: false)],
  pagination: .earlier(Date(), inclusive: true)) { result in
    switch result {
    case .success(let pinnedMessages):
      print(pinnedMessages)
    case .failure(let failure):
      // …
    }
  }
// Next page can be fetched using before message id
let fetchedPinnedMessageId = …
channelController.loadPinnedMessages(
  pagination: .before(fetchedPinnedMessageId, inclusive: false)) { result in
    // …
  }
```

</codetabs-item>

</codetabs>

---

This page was last updated at 2025-08-15T14:14:42.419Z.

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/java/pinned_messages/](https://getstream.io/chat/docs/java/pinned_messages/).