# Unread

The most common use case for client-level events is unread counts. Here's an example of a complete unread count integration for your chat app. As a first step we get the unread count when the user connects:

<codetabs>

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

```js
const user = await client.connectUser({ id: "myid" }, token);

// response.me.total_unread_count returns the unread count
// response.me.unread_channels returns the count of channels with unread messages
```

</codetabs-item>

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

```dart
final response = await client.setUser(user, "{{ chat_user_token }}");

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

</codetabs-item>

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

```kotlin
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
  }
}
```

</codetabs-item>

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

```swift
let currentUserController = client.currentUserController()

class CurrentUserDelegate: CurrentChatUserControllerDelegate {
  func currentUserController(_ controller: CurrentChatUserController,
                didChangeCurrentUserUnreadCount: UnreadCount) {
    // handle unread count change
  }
}

// We need to 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="unreal" label="Unreal">

```cpp
Client->ConnectUser(
  User,
  TEXT("{{ chat_user_token }}"),
  [](const FOwnUser& UserRef)
  {
    const int32 TotalUnreadCount = UserRef.TotalUnreadCount;
    const int32 UnreadChannels = UserRef.UnreadChannels;
  });
```

</codetabs-item>

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

```java
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();
  }
});
```

</codetabs-item>

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

```csharp
public async Task GetUnreadCounts()
{
  // Once user is connected you can access unread counts via IStreamLocalUserData
  var localUserData = Client.LocalUserData;

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

  // It's also returned by the ConnectUserAsync method
  var localUserData2 = await Client.ConnectUserAsync("api_key", "user_id", "user_token");

  // And also returned by the Connected event
  Client.Connected += ClientOnConnected;

  // All above examples returned the same IStreamLocalUserData object which represents the local user connected to the Stream Chat server
}

private void ClientOnConnected(IStreamLocalUserData localUserData)
{
}
```

</codetabs-item>

</codetabs>

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>

While you're using the app, the unread count can change. A user can be added to a channel, a new message can be created, or the user can mark the messages as seen on another tab/device.

<admonition type="info">

Unread counts are only stored and returned at connection time for channel members.

</admonition>

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>

To support updating the unread count in real-time, you can listen to these events:

- `notification.message_new`

- `notification.mark_read`

- `notification.mark_unread`

These two events include the fields  `total_unread_count`  and  `unread_channels` . You can listen to them all at once like this:

<codetabs>

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

```js
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
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
channelClient.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
let currentUserController = client.currentUserController()

class CurrentUserDelegate: CurrentChatUserControllerDelegate {
  func currentUserController(_ controller: CurrentChatUserController,
                didChangeCurrentUserUnreadCount: UnreadCount) {
    // handle unread count change
  }
}

// We need to keep a strong reference to delegate
// since controller only keeps a weak reference
let delegate = CurentUserDelegate()
currentUserController.delegate = delegate

currentUserController.synchronize()
```

</codetabs-item>

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

```cpp
Client->On<FNotificationMessageNewEvent>(
  [](const FNotificationMessageNewEvent& Event)
  {
    const int32 TotalUnreadCount = Event.TotalUnreadCount;
    const int32 UnreadChannels = Event.UnreadChannels;
  });

Client->On<FNotificationMarkReadEvent>(
  [](const FNotificationMarkReadEvent& Event)
  {
    const int32 TotalUnreadCount = Event.TotalUnreadCount;
    const int32 UnreadChannels = Event.UnreadChannels;
  });
```

</codetabs-item>

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

```java
channelClient.subscribeFor(
   new Class[]{
       NewMessageEvent.class,
       NotificationMessageNewEvent.class,
       MarkAllReadEvent.class,
       NotificationMarkReadEvent.class
   },
   event -> {
     if (event instanceof NewMessageEvent) {
       NewMessageEvent newMessageEvent = (NewMessageEvent) event;
       Integer unreadChannels = newMessageEvent.getUnreadChannels();
       Integer totalUnreadCount = newMessageEvent.getTotalUnreadCount();
     } else if (event instanceof NotificationMessageNewEvent) {
       NotificationMessageNewEvent notificationMessageNewEvent = (NotificationMessageNewEvent) event;
       Integer unreadChannels = notificationMessageNewEvent.getUnreadChannels();
       Integer totalUnreadCount = notificationMessageNewEvent.getTotalUnreadCount();
     } else if (event instanceof MarkAllReadEvent) {
       MarkAllReadEvent markAllReadEvent = (MarkAllReadEvent) event;
       Integer unreadChannels = markAllReadEvent.getUnreadChannels();
       Integer totalUnreadCount = markAllReadEvent.getTotalUnreadCount();
     } else if (event instanceof NotificationMarkReadEvent) {
       NotificationMarkReadEvent notificationMarkReadEvent = (NotificationMarkReadEvent) event;
       Integer unreadChannels = notificationMarkReadEvent.getUnreadChannels();
       Integer totalUnreadCount = notificationMarkReadEvent.getTotalUnreadCount();
     }
   }
);
```

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

### Fetch Unread API

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 API

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 couldnt find a user with that ID

</admonition>

---

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

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