# Unread Counts

The following unread counts are provided by Stream

- A total count of unread messages
- Number of unread channels
- A count of unread threads
- Unread @mentions
- Unread messages per channel
- Unread @mentions per channel
- Unread counts by team
- Unread counts by channel type

Unread counts are first fetched when a user connects.
After that they are updated by events. (new message, mark read, delete message, delete channel etc.)

### Reading Unread Counts

Unread counts are returned when a user connects. After that, you can listen to events to keep them updated in real-time.

<codetabs>

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

```js
// Step 1: Get initial unread counts when connecting
const user = await client.connectUser({ id: "myid" }, token);

console.log(user.me.total_unread_count); // total unread messages
console.log(user.me.unread_channels); // number of channels with unread messages
console.log(user.me.unread_threads); // number of unread threads

// Step 2: Listen to events for real-time updates
client.on((event) => {
  if (event.total_unread_count !== undefined) {
    console.log(event.total_unread_count);
  }

  if (event.unread_channels !== undefined) {
    console.log(event.unread_channels);
  }
});
```

</codetabs-item>

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

```dart
// Step 1: Get initial unread counts when connecting
final response = await client.connectUser(user, "{{ chat_user_token }}");

print("There are ${response.me.unreadChannels} unread channels");
print("There are ${response.me.totalUnreadCount} unread messages");

// Step 2: Listen to events for real-time updates
client.on().where((Event event) => event.unreadChannels != null).listen((Event event) {
  print("Unread channels count changed to: ${event.unreadChannels}");
});

client.on().where((Event event) => event.totalUnreadCount != null).listen((Event event) {
  print("Unread messages count changed to: ${event.totalUnreadCount}");
});
```

</codetabs-item>

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

```kotlin
// Step 1: Get initial unread counts when connecting
client.connectUser(User("user-id"), "{{ chat_user_token }}").enqueue { result ->
  if (result.isSuccess) {
    val user = result.data().user
    val unreadChannels = user.unreadChannels
    val totalUnreadCount = user.totalUnreadCount
  }
}

// Step 2: Listen to events for real-time updates
client.subscribeFor(
  NewMessageEvent::class,
  NotificationMessageNewEvent::class,
  MarkAllReadEvent::class,
  NotificationMarkReadEvent::class
) { event ->
  when (event) {
    is NewMessageEvent -> {
      val unreadChannels = event.unreadChannels
      val totalUnreadCount = event.totalUnreadCount
    }
    is NotificationMessageNewEvent -> {
      val unreadChannels = event.unreadChannels
      val totalUnreadCount = event.totalUnreadCount
    }
    is MarkAllReadEvent -> {
      val unreadChannels = event.unreadChannels
      val totalUnreadCount = event.totalUnreadCount
    }
    is NotificationMarkReadEvent -> {
      val unreadChannels = event.unreadChannels
      val totalUnreadCount = event.totalUnreadCount
    }
  }
}
```

</codetabs-item>

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

```swift
// Use the currentUserController to get unread counts and listen for changes
let currentUserController = client.currentUserController()

class CurrentUserDelegate: CurrentChatUserControllerDelegate {
  func currentUserController(_ controller: CurrentChatUserController,
                didChangeCurrentUserUnreadCount: UnreadCount) {
    // Handle unread count change (both initial and real-time updates)
  }
}

// Keep a strong reference to delegate since controller only keeps a weak reference
let delegate = CurrentUserDelegate()
currentUserController.delegate = delegate

currentUserController.synchronize()
```

</codetabs-item>

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

```java
// Step 1: Get initial unread counts when connecting
User user = new User();
user.setId("user-id");
client.connectUser(user, "{{ chat_user_token }}").enqueue(result -> {
  if (result.isSuccess()) {
    User userRes = result.data().getUser();
    int unreadChannels = userRes.getUnreadChannels();
    int totalUnreadCount = userRes.getTotalUnreadCount();
  }
});

// Step 2: Listen to events for real-time updates
client.subscribeFor(
   new Class[]{
       NewMessageEvent.class,
       NotificationMessageNewEvent.class,
       MarkAllReadEvent.class,
       NotificationMarkReadEvent.class
   },
   event -> {
     if (event instanceof NewMessageEvent) {
       NewMessageEvent e = (NewMessageEvent) event;
       Integer unreadChannels = e.getUnreadChannels();
       Integer totalUnreadCount = e.getTotalUnreadCount();
     } else if (event instanceof NotificationMessageNewEvent) {
       NotificationMessageNewEvent e = (NotificationMessageNewEvent) event;
       Integer unreadChannels = e.getUnreadChannels();
       Integer totalUnreadCount = e.getTotalUnreadCount();
     }
     // Handle other event types similarly
   }
);
```

</codetabs-item>

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

```csharp
// Step 1: Get initial unread counts when connecting
var localUserData = await Client.ConnectUserAsync("api_key", "user_id", "user_token");

Debug.Log(localUserData.UnreadChannels);
Debug.Log(localUserData.TotalUnreadCount);

// You can also access unread counts via Client.LocalUserData after connection
// Or subscribe to the Connected event for real-time updates
Client.Connected += (IStreamLocalUserData userData) =>
{
  Debug.Log(userData.UnreadChannels);
  Debug.Log(userData.TotalUnreadCount);
};
```

</codetabs-item>

</codetabs>

Note that the higher level SDKs offer convenient endpoints for this. Hooks on react, stateflow on Android etc.
So you only need to use the events manually if you're using plain JS.

### Unread Counts - Server side

The unread endpoint can fetch unread counts server-side, eliminating the need for a client-side connection. It can also be used client-side without requiring a persistent connection to the chat service. This can be useful for including an unread count in notifications or for gently polling when a user loads the application to keep the client up to date without loading up the entire chat.

<admonition type="info">

A user_id whose unread count is fetched through this method is automatically counted as a Monthly Active User. This may affect your bill.

</admonition>

<codetabs>

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

```js
const response = await client.getUnreadCount(userID);
console.log(response.total_unread_count); // total unread count for user
console.log(response.channels); // distribution of unread counts across channels
console.log(response.channel_type); // distribution of unread counts across channel types
console.log(response.total_unread_threads_count); // total unread threads
console.log(response.threads); // list of unread counts per thread
```

</codetabs-item>

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

```python
response = client.unread_counts(userID)
print(response["total_unread_count"]) # total unread count for user
print(response["channels"]) # distribution of unread counts across channels
print(response["channel_type"]) # distribution of unread counts across channel types
print(response["total_unread_threads_count"]) # total unread threads
print(response["threads"]) # list of unread counts per thread
```

</codetabs-item>

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

```php
$response = $client->unreadCounts(userID);
echo $response["total_unread_count"]; // total unread count for user
echo $response["channels"]; // distribution of unread counts across channels
echo $response["channel_type"]; // distribution of unread counts across channel types
echo $response["total_unread_threads_count"]; // total unread threads
echo $response["threads"]; // list of unread counts per thread
```

</codetabs-item>

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

```go
resp, err := client.UnreadCounts(context.Background(), userID)
if err != nil {
	panic(err)
}
fmt.Println(resp.TotalUnreadCount)
fmt.Println(resp.Channels)
fmt.Println(resp.ChannelType)
fmt.Println(resp.TotalUnreadThreadsCount)
fmt.Println(resp.Threads)
```

</codetabs-item>

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

```csharp
var current = await Client.GetLatestUnreadCountsAsync();

Debug.Log(current.TotalUnreadCount); // Total unread messages
Debug.Log(current.TotalUnreadThreadsCount); // Total unread threads

foreach (var unreadChannel in current.UnreadChannels)
{
  Debug.Log(unreadChannel.ChannelCid); // CID of the channel with unread messages
  Debug.Log(unreadChannel.UnreadCount); // Count of unread messages
  Debug.Log(unreadChannel.LastRead); // Datetime of the last read message
}

foreach (var unreadChannelByType in current.UnreadChannelsByType)
{
  Debug.Log(unreadChannelByType.ChannelType); // Channel type
  Debug.Log(unreadChannelByType.ChannelCount); // How many channels of this type have unread messages
  Debug.Log(unreadChannelByType.UnreadCount); // How many unread messages in all channels of this type
}

foreach (var unreadThread in current.UnreadThreads)
{
  Debug.Log(unreadThread.ParentMessageId); // Message ID of the parent message for this thread
  Debug.Log(unreadThread.LastReadMessageId); // Last read message in this thread
  Debug.Log(unreadThread.UnreadCount); // Count of unread messages
  Debug.Log(unreadThread.LastRead); // Datetime of the last read message
}
```

</codetabs-item>

</codetabs>

<admonition type="info">

This endpoint will return the last 100 unread channels, they are sorted by last_message_at.

</admonition>

#### Batch Fetch Unread

The batch unread endpoint works the same way as the non-batch version with the exception that it can handle multiple user IDs at once and that it is restricted to server-side only.

<codetabs>

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

```js
const response = await client.getUnreadCountBatch([userID1, userID2]);
console.log(response.counts_by_user[userID1].total_unread_count); // total unread count for userID1
console.log(response.counts_by_user[userID1].channels); // distribution of unread counts across channels for userID1
console.log(response.counts_by_user[userID1].channel_type); // distribution of unread counts across channel types for userID1
console.log(response.counts_by_user[userID1].total_unread_threads_count); // total unread threads count for userID1
console.log(response.counts_by_user[userID1].threads); // list of unread counts per thread for userID1
```

</codetabs-item>

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

```python
response = client.unread_counts_batch([userID1, userID2])
print(response["counts_by_user"][userID1]["total_unread_count"]) # total unread count for userID1
print(response["counts_by_user"][userID1]["channels"]) # distribution of unread counts across channels for userID1
print(response["counts_by_user"][userID1]["channel_type"]) # distribution of unread counts across channel types for userID1
print(response["counts_by_user"][userID1]["total_unread_threads_count"]) # total unread threads count for userID1
print(response["counts_by_user"][userID1]["threads"]) # list of unread counts per thread for userID1
```

</codetabs-item>

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

```php
$response = $client->unreadCountsBatch([$userID1, $userID2]);
echo $response["counts_by_user"][$userID1]["total_unread_count"]; // total unread count for userID1
echo $response["counts_by_user"][$userID1]["channels"]; // distribution of unread counts across channels for userID1
echo $response["counts_by_user"][$userID1]["channel_type"]; // distribution of unread counts across channel types for userID1
echo $response["counts_by_user"][$userID1]["total_unread_threads_count"]; // total unread threads count for userID1
echo $response["counts_by_user"][$userID1]["threads"]; // list of unread counts per thread for userID1
```

</codetabs-item>

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

```go
resp, err := client.UnreadCountsBatch(context.Background(), []string{userID1, userID2})
if err != nil {
	panic(err)
}
fmt.Println(resp.CountsByUser[userID1].TotalUnreadCount)
fmt.Println(resp.CountsByUser[userID1].Channels)
fmt.Println(resp.CountsByUser[userID1].ChannelType)
fmt.Println(resp.CountsByUser[userID1].TotalUnreadThreadsCount)
fmt.Println(resp.CountsByUser[userID1].Threads)
```

</codetabs-item>

</codetabs>

<admonition type="info">

If a user ID is not returned in the response then the API couldn't find a user with that ID

</admonition>

### Mark Read

By default the UI component SDKs (React, React Native, ...) mark messages as read automatically when the channel is visible. You can also make the call manually like this:

<codetabs>

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

```js
// mark all messages on a channel as read
await channel.markRead();
```

</codetabs-item>

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

```dart
await channel.markRead();
```

</codetabs-item>

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

```kotlin
channelClient.markRead().enqueue { result ->
  if (result.isSuccess) {
    // Messages in the channel marked as read
  } else {
    // Handle result.error()
  }
}
```

</codetabs-item>

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

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

channelController.markRead()
```

</codetabs-item>

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

```cpp
Channel->MarkRead();
```

</codetabs-item>

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

```java
channelClient.markRead().enqueue(result -> {
  if (result.isSuccess()) {
    // Messages in the channel marked as read
  } else {
    // Handle result.error()
  }
});
```

</codetabs-item>

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

```csharp
await message.MarkMessageAsLastReadAsync();
```

</codetabs-item>

</codetabs>

The `markRead` function can also be executed server-side by passing a user ID as shown in the example below:

<codetabs>

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

```js
// mark all messages on a channel as read (server side)
await channel.markRead({ user_id: "foo" });
```

</codetabs-item>

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

```php
// mark all messages on a channel as read (server side)
$mark = $channel->markRead('user-id');
```

</codetabs-item>

</codetabs>

It's also possible to mark an already read message as unread:

<codetabs>

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

```js
await channel.markUnread({ message_id: "<message_id>" });
```

</codetabs-item>

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

```swift
channelController.markUnread(from: "message-id") { result in
  // …
}
```

</codetabs-item>

</codetabs>

The mark unread operation can also be executed server-side by passing a user ID:

<codetabs>

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

```js
await channel.markUnread({ message_id: "<message_id>", user_id: "<user_id>" });
```

</codetabs-item>

</codetabs>

#### Mark All As Read

You can mark all channels as read for a user like this:

<codetabs>

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

```js
// client-side
await client.markAllRead();

// mark all as read for one user server-side
await serverSideClient.markAllRead({ user: { id: "myid" } });
```

</codetabs-item>

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

```php
// mark all messages on all channels as read for one user, server-side
$client->markAllRead('user-id');
```

</codetabs-item>

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

```dart
await client.markRead();
```

</codetabs-item>

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

```swift
let channelListController = client.channelListController(query: .init(filter: .containMembers(userIds: ["joe"])))
channelListController.markAllRead()
```

</codetabs-item>

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

```kotlin
client.markAllRead().enqueue { result ->
  if (result.isSuccess) {
    //Handle success
  } else {
    //Handle failure
  }
}
```

</codetabs-item>

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

```cpp
Client->MarkAllRead();
```

</codetabs-item>

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

```java
client.markAllRead().enqueue((result) -> {
  if (result.isSuccess()) {
    //Handle success
  } else {
    //Handle failure
  }
});
```

</codetabs-item>

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

```csharp
// Mark this message as last read
await message.MarkMessageAsLastReadAsync();

// Mark whole channel as read
await channel.MarkChannelReadAsync();
```

</codetabs-item>

</codetabs>

## Read State - Showing how far other users have read

When you retrieve a channel from the API (e.g. using query channels), the read state for all members is included in the response. This allows you to display which messages are read by each user. For each member, we include the last time they marked the channel as read.

<codetabs>

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

```js
const channel = client.channel("messaging", "test");
await channel.watch();

console.log(channel.state.read);

//{ '2fe6019c-872f-482a-989e-ecf4f786501b':
// { user:
//  {
//   id: '2fe6019c-872f-482a-989e-ecf4f786501b',
//   role: 'user',
//   created_at: '2019-04-24T13:09:19.664378Z',
//   updated_at: '2019-04-24T13:09:23.784642Z',
//   last_active: '2019-04-24T13:09:23.781641Z',
//   online: true
//  },
//  last_read: '2019-04-24T13:09:21.623Z',
// }
//}
```

</codetabs-item>

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

```dart
final response = await channel.watch();

// readState is the list of read states for each user on the channel
List<Read> readState = response.read;
```

</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?.reads
  }
}
```

</codetabs-item>

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

```kotlin
// Get channel
val queryChannelRequest = QueryChannelRequest().withState()

client.queryChannel(
  channelType = "channel-type",
  channelId = "channel-id",
  request = queryChannelRequest,
).enqueue { result ->
  if (result.isSuccess) {
    // readState is the list of read states for each user on the channel
    val readState: List<ChannelUserRead> = result.data().read
  } else {
    // Handle result.error()
  }
}
```

</codetabs-item>

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

```cpp
const FChannelProperties Properties{TEXT("channel-type"), TEXT("channel-id")};
Client->WatchChannel(
  Properties,
  [](UChatChannel* Channel)
  {
    // ReadState is the list of read states for each user on the channel
    const TArray<FRead>& ReadState = Channel->State.Read;
  });
```

</codetabs-item>

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

```java
// Get channel
QueryChannelRequest queryChannelRequest = new QueryChannelRequest().withState();

client.queryChannel("channel-type", "channel-id", queryChannelRequest).enqueue((result) -> {
  if (result.isSuccess()) {
    // readState is the list of read states for each user on the channel
    List<ChannelUserRead> readState = result.data().getRead();
  } else {
    // Handle result.error()
  }
});
```

</codetabs-item>

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

```csharp
// Every channel maintains a full list of read state for each channel member
foreach (var read in channel.Read)
{
  Debug.Log(read.User); // User
  Debug.Log(read.UnreadMessages); // How many unread messages
  Debug.Log(read.LastRead); // Last read date
}
```

</codetabs-item>

</codetabs>

### Unread Messages Per Channel

You can retrieve the count of unread messages for the current user on a channel like this:

<codetabs>

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

```js
channel.countUnread();
```

</codetabs-item>

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

```dart
channel.unreadCount();
```

</codetabs-item>

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

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

class ChannelDelegate: ChatChannelControllerDelegate {
  func channelController(_ channelController: ChatChannelController,
              didUpdateChannel channel: EntityChange<ChatChannel>) {
    channelController.channel?.unreadCount
  }
}

channelController.delegate = ChannelDelegate()

channelController.synchronize()
```

</codetabs-item>

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

```kotlin
// Get channel
val queryChannelRequest = QueryChannelRequest().withState()

client.queryChannel(
  channelType = "channel-type",
  channelId = "channel-id",
  request = queryChannelRequest,
).enqueue { result ->
  if (result.isSuccess) {
    // Unread count for current user
    val unreadCount = result.data().unreadCount
  } else {
    // Handle result.error()
  }
}
```

</codetabs-item>

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

```cpp
Channel->State.UnreadCount();
```

</codetabs-item>

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

```java
// Get channel
QueryChannelRequest queryChannelRequest = new QueryChannelRequest().withState();

client.queryChannel("channel-type", "channel-id", queryChannelRequest).enqueue((result) -> {
  if (result.isSuccess()) {
    // Unread count for current user
    Integer unreadCount = result.data().getUnreadCount();
  } else {
    // Handle result.error()
  }
});
```

</codetabs-item>

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

```csharp
// Every channel maintains a full list of read state for each channel member
foreach (var read in channel.Read)
{
  Debug.Log(read.User); // User
  Debug.Log(read.UnreadMessages); // How many unread messages
  Debug.Log(read.LastRead); // Last read date
}
```

</codetabs-item>

</codetabs>

### Unread Mentions Per Channel

You can retrieve the count of unread messages mentioning the current user on a channel like this:

<codetabs>

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

```js
channel.countUnreadMentions();
```

</codetabs-item>

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

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

class ChannelDelegate: ChatChannelControllerDelegate {
  func channelController(_ channelController: ChatChannelController,
              didUpdateChannel channel: EntityChange<ChatChannel>) {
    channelController.channel?.unreadCount.mentionedMessages
  }
}

channelController.delegate = ChannelDelegate()

channelController.synchronize()
```

</codetabs-item>

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

```kotlin
// Get channel
val queryChannelRequest = QueryChannelRequest().withState()
val currentUser = client.getCurrentUser()
if (currentUser == null) {
  // Handle user not connected state
  return
}

client.queryChannel(
  channelType = "channel-type",
  channelId = "channel-id",
  request = queryChannelRequest,
).enqueue { result ->
  if (result.isSuccess) {
    // Unread mentions
    val channel = result.data()
    val unreadCount = channel.countUnreadMentionsForUser(currentUser)
  } else {
    // Handle result.error()
  }
}
```

</codetabs-item>

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

```java
// Get channel
QueryChannelRequest queryChannelRequest = new QueryChannelRequest().withState();
User currentUser = client.getCurrentUser();
if (currentUser == null) {
  // Handle user not connected state
  return;
}

client.queryChannel("channel-type", "channel-id", queryChannelRequest).enqueue((result) -> {
  if (result.isSuccess()) {
    // Unread mentions
    Channel channel = result.data();
    Integer unreadCount = ChannelExtensionKt.countUnreadMentionsForUser(channel, currentUser);
  } else {
    // Handle result.error()
  }
});
```

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


---

This page was last updated at 2026-03-05T19:06:20.472Z.

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