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.)
Unread counts are returned when a user connects. After that, you can listen to events to keep them updated in real-time.
// Step 1: Get initial unread counts when connectingconst user = await client.connectUser({ id: "myid" }, token);console.log(user.me.total_unread_count); // total unread messagesconsole.log(user.me.unread_channels); // number of channels with unread messagesconsole.log(user.me.unread_threads); // number of unread threads// Step 2: Listen to events for real-time updatesclient.on((event) => { if (event.total_unread_count !== undefined) { console.log(event.total_unread_count); } if (event.unread_channels !== undefined) { console.log(event.unread_channels); }});
// Step 1: Get initial unread counts when connectingfinal 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 updatesclient.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}");});
// Step 1: Get initial unread counts when connectingclient.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 updatesclient.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 } }}
// Use the currentUserController to get unread counts and listen for changeslet 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 referencelet delegate = CurrentUserDelegate()currentUserController.delegate = delegatecurrentUserController.synchronize()
// Step 1: Get initial unread counts when connectingUser 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 updatesclient.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 });
// Step 1: Get initial unread counts when connectingvar 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 updatesClient.Connected += (IStreamLocalUserData userData) =>{ Debug.Log(userData.UnreadChannels); Debug.Log(userData.TotalUnreadCount);};
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.
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.
A user_id whose unread count is fetched through this method is automatically counted as a Monthly Active User. This may affect your bill.
const response = await client.getUnreadCount(userID);console.log(response.total_unread_count); // total unread count for userconsole.log(response.channels); // distribution of unread counts across channelsconsole.log(response.channel_type); // distribution of unread counts across channel typesconsole.log(response.total_unread_threads_count); // total unread threadsconsole.log(response.threads); // list of unread counts per thread
response = client.chat.unread_counts(user_id=userID)print(response.data.total_unread_count) # total unread count for userprint(response.data.channels) # distribution of unread counts across channelsprint(response.data.channel_type) # distribution of unread counts across channel typesprint(response.data.total_unread_threads_count) # total unread threadsprint(response.data.threads) # list of unread counts per thread
$response = $client->unreadCounts($userID);$data = $response->getData();echo $data->totalUnreadCount; // total unread count for userecho $data->channels; // distribution of unread counts across channelsecho $data->channelType; // distribution of unread counts across channel typesecho $data->totalUnreadThreadsCount; // total unread threadsecho $data->threads; // list of unread counts per thread
var current = await Client.GetLatestUnreadCountsAsync();Debug.Log(current.TotalUnreadCount); // Total unread messagesDebug.Log(current.TotalUnreadThreadsCount); // Total unread threadsforeach (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}
This endpoint will return the last 100 unread channels, they are sorted by last_message_at.
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.
const response = await client.getUnreadCountBatch([userID1, userID2]);console.log(response.counts_by_user[userID1].total_unread_count); // total unread count for userID1console.log(response.counts_by_user[userID1].channels); // distribution of unread counts across channels for userID1console.log(response.counts_by_user[userID1].channel_type); // distribution of unread counts across channel types for userID1console.log(response.counts_by_user[userID1].total_unread_threads_count); // total unread threads count for userID1console.log(response.counts_by_user[userID1].threads); // list of unread counts per thread for userID1
response = client.chat.unread_counts_batch(user_ids=[userID1, userID2])print(response.data.counts_by_user[userID1].total_unread_count) # total unread count for userID1print(response.data.counts_by_user[userID1].channels) # distribution of unread counts across channels for userID1print(response.data.counts_by_user[userID1].channel_type) # distribution of unread counts across channel types for userID1print(response.data.counts_by_user[userID1].total_unread_threads_count) # total unread threads count for userID1print(response.data.counts_by_user[userID1].threads) # list of unread counts per thread for userID1
$response = $client->unreadCountsBatch(new Models\UnreadCountsBatchRequest( userIds: [$userID1, $userID2],));$data = $response->getData();echo $data->countsByUser[$userID1]->totalUnreadCount; // total unread count for userID1echo $data->countsByUser[$userID1]->channels; // distribution of unread counts across channels for userID1echo $data->countsByUser[$userID1]->channelType; // distribution of unread counts across channel types for userID1echo $data->countsByUser[$userID1]->totalUnreadThreadsCount; // total unread threads count for userID1echo $data->countsByUser[$userID1]->threads; // list of unread counts per thread for userID1
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:
// mark all messages on a channel as readawait channel.markRead();
await channel.markRead();
channelClient.markRead().enqueue { result -> if (result.isSuccess) { // Messages in the channel marked as read } else { // Handle result.error() }}
let channelController = client.channelController(for: .init(type: .messaging, id: "general"))channelController.markRead()
Channel->MarkRead();
channelClient.markRead().enqueue(result -> { if (result.isSuccess()) { // Messages in the channel marked as read } else { // Handle result.error() }});
await message.MarkMessageAsLastReadAsync();
The markRead function can also be executed server-side by passing a user ID as shown in the example below:
// mark all messages on a channel as read (server side)await channel.markRead({ user_id: "foo" });
// mark all messages on a channel as read (server side)$client->markRead("messaging", "general", new Models\MarkReadRequest(userID: "user-id"));
It's also possible to mark an already read message as unread:
You can mark all channels as read for a user like this:
// client-sideawait client.markAllRead();// mark all as read for one user server-sideawait serverSideClient.markAllRead({ user: { id: "myid" } });
// mark all messages on all channels as read for one user, server-side$client->markChannelsRead(new Models\MarkChannelsReadRequest(userID: "user-id"));
await client.markRead();
let channelListController = client.channelListController(query: .init(filter: .containMembers(userIds: ["joe"])))channelListController.markAllRead()
client.markAllRead().enqueue { result -> if (result.isSuccess) { //Handle success } else { //Handle failure }}
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.
final response = await channel.watch();// readState is the list of read states for each user on the channelList<Read> readState = response.read;
let channelController = client.channelController(for: .init(type: .messaging, id: "general"))channelController.synchronize() { error in if error == nil { channelController.channel?.reads }}
// Get channelval 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() }}
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; });
// Get channelQueryChannelRequest 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() }});
// Every channel maintains a full list of read state for each channel memberforeach (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}
// Get channelval 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() }}
Channel->State.UnreadCount();
// Get channelQueryChannelRequest 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() }});
// Every channel maintains a full list of read state for each channel memberforeach (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}