Skip to main content
Available from Chat SDK v5.0.7. These APIs require cometchat_sdk v5.0.7 or later. See Setup to upgrade.
Thread subscription gives users control over thread noise. A user can subscribe to a message thread to be notified of future replies, or unsubscribe to mute it. The server subscribes a user to a thread automatically when they start it, reply in it, or are @-mentioned in it — and they can explicitly subscribe to any parent message, even one that has no replies yet.
Thread subscription builds on Threaded Messages. A thread is identified by the ID of its parent message — there is no separate thread ID.

How State Works

The SDK keeps no subscription state of its own. There is no cache and no listener to reconcile:
  • Message-list fetches and getMessageDetails() ask the server for the flag, and it arrives on the message as the threadSubscribed field.
  • subscribeToThread() and unsubscribeFromThread() complete when the server has accepted the change. The onSuccess callback is the acknowledgement — there is no follow-up event.
Your app owns the resulting UI state. That means you decide when to flip a toggle optimistically, and you decide what a thread’s state is before you have fetched it.

Subscribe to a Thread

Use subscribeToThread() with the ID of the thread’s parent message. The call is idempotent — subscribing to a thread the user already follows succeeds silently. Subscribing to a message with zero replies is allowed; the user is notified when the first reply arrives.

Unsubscribe from a Thread

Use unsubscribeFromThread(). This is idempotent too — unsubscribing from a thread the user does not follow succeeds silently.
Unsubscribing is not sticky. If the user replies in the thread again, or is @-mentioned in it, the server re-subscribes them. Do not promise users that they will never hear about the thread again.
Unsubscribing hard-deletes the subscription server-side, so a thread you are showing in a “following” list should be removed from that list when the call resolves.

Read the Subscription State

The state rides the parent message. Read it from the threadSubscribed field:
MessagesRequest fetches and getMessageDetails() send withThreadSubscribed=true, so any message you obtained from them carries the flag. Other responses do not ask for it — the message returned by pinMessage() or saveMessage(), and a conversation’s last message — so those read false. The flag is per-viewer: the same message yields different values for different users.
A message delivered over the socket, or returned by a call that did not ask for the flag, reads false. That is not a claim that the user is unsubscribed — it means nobody asked. Because subscribing is idempotent, rendering that false as the “Subscribe” affordance is safe: an unnecessary subscribe is harmless.

Re-read the State for One Message

When you need an authoritative flag for a single message — after acting on a socket-delivered message, or for a deep link to a thread you have not fetched — fetch the parent message with getMessageDetails() and read the flag off the result.

You Are Subscribed to Your Own Messages

Sending a message subscribes you to the thread it may later grow — there is nothing to call. A message you sent comes back with threadSubscribed set to true on later fetches, and only for you: the flag is per-viewer, so the same message reads false for everybody else until they subscribe themselves. That default is what makes the flag meaningful on your own messages. Since it starts out true, a false on a message you sent — read from a fetch, not the socket — is not silence. It means you unsubscribed, and nothing should quietly put you back. This only holds for a message you sent and obtained from a fetch that asked for the flag. On anyone else’s message, or on anything socket-delivered, false still just means the server was not asked.

Keeping Your Own Copies in Sync

The same thread can be represented by several message objects at once — a row in the message list, the header of an open thread view, an entry in a thread inbox. Because the SDK caches nothing, align the copies you hold once you know the answer. threadSubscribed is a mutable field for exactly this:
Assigning threadSubscribed is local only — it changes the object in memory and sends nothing to the server. Use subscribeToThread() / unsubscribeFromThread() to change the actual subscription.

Reacting to Replies

A thread reply is an ordinary message with parentMessageId set, delivered through the standard MessageListener alongside every other message. There is no separate thread listener.
Using MessageListener also gets you onMessageEdited and onMessageDeleted for replies, which a thread-only channel would not. Replies sent from this device do not arrive on a listener — bump your thread row from the send call’s onSuccess callback instead. Replies the same user sends from another device do arrive on MessageListener.

Fetch the Threads a User Participates In

Use ThreadsRequest to build a thread inbox. Every returned thread is one the logged-in user is subscribed to — presence in the list is a subscription. ThreadsRequest, MessageThread and ConversationListener are exported from package:cometchat_sdk/cometchat_sdk.dart, not from the modular core.dart or messaging.dart entry points.
fetchNext() takes required onSuccess and onError callbacks, like every other request in the SDK. Call it repeatedly on the same object to page through the list, using hasMore() as the loop condition — page on it rather than on the size of the last result. Once the list is exhausted, fetchNext() resolves an empty list.
Scope the list to one conversation with setUid() for a one-on-one counterpart or setGuid() for a group. The two are mutually exclusive.
If you insert a row into the list yourself — for example after the user subscribes to a thread from the message view — call threadsRequest.markSeen(parentMessageId) so the next fetchNext() does not return it again as a duplicate.
A ThreadsRequest is single-use and one-directional: it accumulates its paging state internally and has no reset. To refresh a list, build a new request from the builder and replace the list rather than re-running an exhausted one. This is the same contract as ConversationsRequest and MessagesRequest.

The MessageThread Model

Each row is a MessageThread:
Sort a thread inbox on lastReply?.sentAt falling back to parentMessage?.sentAt — a thread with no replies has no last reply, and updatedAt is a cursor, not a sort key. A row carries only the raw receiverUid, with no name or avatar: render the ID immediately and resolve the display name lazily with CometChat.getUser() or CometChat.getGroup() rather than dropping the row.

Notification Preferences

The notification preference for replies carries a value that pairs with this feature, so a user can be notified only about the threads they follow: SUBSCRIBE_TO_SUBSCRIBED_THREADS in the RepliesOptions enum. Threads exist in one-on-one conversations as well as groups, so set it on whichever preferences you are updating:
updatePreferences() merges what you set, so sending only the fields you changed is enough.
A threaded reply (a message posted into a thread) and a quoted reply (a reply to one specific message) are configured through two different enums — RepliesOptions and QuotedRepliesOptions — and their fourth values differ. The quotedReplies field on GroupPreferences and OneOnOnePreferences is typed QuotedRepliesOptions, so passing a RepliesOptions value there does not compile.
See Notification Preferences for reading and updating a user’s preferences, and for the full QuotedRepliesOptions list.

Error Handling

The calls on this page report failures through onError with a CometChatException. Branch on e.code rather than the message text:

Next Steps

Threaded Messages

Send, receive and fetch messages inside a thread

All Real Time Listeners

Every listener the SDK exposes, in one place

Mentions

Mention users in messages

Notification Preferences

Read and update a user’s notification preferences