Channel and channel state
A Channel is the client's object for one channel. Its data, members, read state and typing users are in the store channel.state. Its messages, pinned messages, composer and message operations are objects it owns. This page lists the API of Channel and ChannelState in stream-chat.
What a channel holds, and why, is explained on What channels and threads hold. Getting and loading a channel is explained on Shared entities.
Getting a channel
The client keeps one Channel per cid. Get it from the client instead of constructing it with new Channel():
| Call | Returns |
|---|---|
client.channel(type, id, custom) |
The stored channel for type:id, or a new one that the client stores from then on. Sends no request. id and custom are optional. client.channel(type, { members }) creates a channel from its members, without an id. |
client.channelManager.get(cid) |
The stored channel, or undefined. A stored channel can be one that was disposed. |
Channel.watch(options) |
Watches and loads the channel unless that's done already, and resolves with the instance to use from then on. options is optional. |
A few rules apply to client.channel():
- It throws without a user. Call it after
client.connectUser(), which doesn't have to finish first. - For a channel the client already stores, it applies only the
customfield of its last argument and ignores the rest. - A stored channel that was disposed is replaced by a new instance.
- Without an
id,memberscreates a distinct channel between those members. It's provisional until its first query answers. A channel with neither isn't stored.
This opens "general" and watches it, whether or not another part of the app is opening it at the same time:
const general = await client.channel("messaging", "general").watch();A channel identifies itself with these properties:
| Property | What it holds |
|---|---|
cid |
type:id. A channel created from its members has a temporary cid until the server assigns its id. |
id |
The channel id, or undefined while the channel is provisional. |
type |
The channel type, such as messaging. |
isProvisional |
true while the channel has no id. Not in a store: subscribe to initialized, which the same response sets. |
The objects a channel owns
| Property | What it is |
|---|---|
channel.state |
The ChannelState store described below. |
channel.messagePaginator |
The channel's message list. See Message paginator. |
channel.pinnedMessagesPaginator |
The channel's pinned messages. See Pinned messages paginator. |
channel.messageComposer |
The composer for new messages. See MessageComposer API. |
channel.messageOperations |
Sends, retries, edits and deletes messages, and adds reactions. See Message operations. |
channel.messageReceiptsTracker |
Who read and who received each message. See Message receipts tracker. |
channel.cooldownTimer |
The slow mode countdown for the current user. See Slow mode. |
A thread has its own message list, composer and message operations. Inside a thread, use the thread's, not the channel's.
channel.state fields
channel.state is a ChannelState, a store of ChannelStateData. Every field below is in that one store, so a subscription selects the fields it needs:
| Field | Type | What it holds |
|---|---|---|
data |
Partial<ChannelResponse> or undefined |
The channel's server data: name, image, frozen, hidden, cooldown, member_count, own_capabilities, custom and the rest. Before the first load, the data passed to client.channelManager.ensure(). |
members |
Record<string, ChannelMemberResponse> |
The loaded members, by user id. A large channel loads only some of them. |
memberCount |
number |
The total member count, from data.member_count. |
membership |
ChannelMemberResponse |
The current user's own membership: channel_role, archived_at, pinned_at and the rest. An empty object until it's loaded. |
watchers |
Record<string, UserResponse> |
The users watching the channel now, by user id. |
watcherCount |
number |
How many users are watching. |
watchStatus |
ChannelWatchStatus |
Whether this client watches the channel: "watching", "wasWatching" (the connection dropped) or "notWatching". |
read |
Record<string, ...> |
Each member's read state, by user id. The fields are in the next table. |
typing |
Record<string, Event> |
Who is typing, by user id: each user's latest typing.start event. |
ownCapabilities |
string[] |
What the current user may do here, such as send-message or upload-file. |
muteStatus |
ChannelMuteStatus |
{ muted, createdAt, expiresAt }. The dates are in nanoseconds, or null. |
initialized |
boolean |
A live server response was applied to this instance. Data from the offline database doesn't count. |
offlineMode |
boolean |
The data came from the offline database, not from the server. |
active |
boolean |
Something has activated the channel with Channel.activate() and not released it yet. |
pendingDisposal |
boolean |
The instance was disposed and can't be used any more. |
supersededBy |
Channel or undefined |
The instance that replaced this one. See Superseded channels. |
aiState |
AIState |
The state of an AI response: one of the AIStates values, AIStates.Idle by default. |
Each entry of read has these fields. The timestamps are in nanoseconds:
| Field | What it holds |
|---|---|
user |
The user. |
last_read |
When the user last read the channel. |
unread_messages |
How many messages the user hasn't read. |
last_read_message_id |
The last message the user read, when the server knows it. |
first_unread_message_id |
The message the user marked unread from, after a mark unread. |
last_delivered_at |
When a message was last delivered to the user. |
last_delivered_message_id |
The last message delivered to the user. |
Read state and unread counts explains which events change it.
This handler runs whenever the member count or the current user's unread count changes:
const ownUserId = client.userId;
const unsubscribe = channel.state.subscribeWithSelector(
({ memberCount, read }) => ({
memberCount,
unread: ownUserId ? (read[ownUserId]?.unread_messages ?? 0) : 0,
}),
({ memberCount, unread }) => {
console.log(`${memberCount} members, ${unread} unread`);
},
);Getters
These getters return the value at the moment you read them, and don't tell you when it changes. To follow a change, subscribe to channel.state.
| Getter | What it reads |
|---|---|
channel.data |
data |
channel.initialized, channel.offlineMode |
initialized, offlineMode |
channel.watchStatus, channel.active |
watchStatus, active |
channel.pendingDisposal, channel.supersededBy |
pendingDisposal, supersededBy |
channel.state.members, channel.state.membership |
members, membership |
channel.state.read, channel.state.watchers |
read, watchers |
channel.state.member_count, channel.state.watcher_count |
memberCount, watcherCount |
channel.state.typing |
The typing users, as the channel's composer holds them |
channel.state.unreadCount |
The current user's unread_messages, or 0 |
channel.state.pending_messages holds the channel's pending messages from the last query. It isn't in the store.
Some of these have setters. They exist for the SDK: app code changes a channel through the methods below, not by writing its state.
Configuration
A channel has two views of its settings:
channel.configis the configuration in effect: the channel type's server settings combined with what the app registered inclient.config.channel.configStateis the same value as a store.channel.serverConfigis the channel type's settings as the server sent them (ChannelConfigWithInfo):typing_events,read_events,replies,uploads,url_enrichment,polls,max_message_length,commandsand the rest. It'sundefineduntil the channel has been queried or watched.
Check channel.config to decide whether a feature is available. Either side can turn a feature off, and only config includes both. channel.config has these fields:
| Field | Default | Combined with the server's |
|---|---|---|
typingEvents.enabled |
true |
typing_events |
readEvents.enabled |
true |
read_events |
readEvents.localUnreadCountEnabled |
false |
Nothing. Counts unread messages locally while read events are off. |
replies.enabled |
true |
replies |
userMessageReminders.enabled |
true |
user_message_reminders |
deliveryEvents.enabled |
true |
delivery_events |
availableCommands |
[] |
commands, which it copies. The app can't set it. |
requestHandlers |
none | Nothing. Replaces the requests for send, retry, update, delete and mark read. |
Change these through client.config, under the channel key. A channel has no updateConfig():
client.config.set({
channel: {
readEvents: { enabled: false, localUnreadCountEnabled: true },
typingEvents: { enabled: false },
},
});The channel key also holds messagePaginator, pinnedMessagesPaginator and messageOperations, which go to the objects the channel owns. Configuration explains how settings resolve, and Configuration tree lists every key.
Loading and lifecycle methods
| Method | Returns | What it does |
|---|---|---|
watch(options, requestOptions) |
Promise<Channel> |
Loads the channel, its members, read state and latest messages, and watches it. Safe to call again, even from several places at once: it sends a request only when the channel isn't loaded yet, or when options differ from those it was watched with. Resolves with this instance or the one that superseded it. Both arguments are optional. |
query(options) |
Promise<ChannelStateResponse> |
Loads the channel with the options you pass. Watches it only with watch: true. The messages it returns aren't added to the message list. |
create(options) |
Promise<ChannelStateResponse> |
Creates the channel on the server, or gets it, without watching it. Asks for no messages, members or read state. |
reload() |
Promise<void> |
Watches again and refreshes the newest loaded messages, keeping messages that failed to send. Use it to refresh a watched channel: repeating watch() doesn't. Does nothing for a channel that was never loaded. |
stopWatching(request, requestOptions) |
Promise<Response> |
Ends the watch. watchStatus becomes "notWatching", and a reconnect doesn't restore it. |
activate() |
() => void |
Marks the channel active and returns the function that releases it. Activations are counted. |
disconnect() |
void |
Disposes the instance: pendingDisposal becomes true. Sends no request, so call stopWatching() first to end the watch. |
getClient() |
StreamChat |
The client. Throws once the channel is disposed. |
options for Channel.watch(), Channel.query() and Channel.create() is a ChannelGetOrCreateRequest: messages, members and watchers pagination, presence, state, watch, data and a few more. Channel.watch() and any query with watch: true wait for the WebSocket to connect. Shared entities explains loading, and Entity lifecycle explains activation and disposal.
This keeps a channel loaded while a screen shows it:
const openScreen = async (id: string) => {
const opened = await client.channel("messaging", id).watch();
// Until release() is called, reconnects reload the channel.
const release = opened.activate();
return { channel: opened, close: release };
};Channel actions
Most methods below send a request. Unless the table says otherwise, the change shows when the server's event arrives, not when the call returns. Most of them take an optional requestOptions as their last argument, which carries an abort signal. Channel.banUser(), Channel.unbanUser(), Channel.markReadViaReporter() and Channel.sendSharedLocation() don't. What's optimistic and what isn't lists how each action settles.
The current user's membership
| Method | What it does |
|---|---|
archive(), unarchive() |
Sets or clears membership.archived_at, on member.updated. |
pin(), unpin() |
Sets or clears membership.pinned_at, on member.updated. |
mute({ expiration }) |
Mutes the channel, for expiration milliseconds if given. muteStatus changes on notification.channel_mutes_updated. |
unmute() |
Unmutes the channel, on the same event. |
muteStatus() |
Returns the current ChannelMuteStatus, without a request. Throws for a provisional channel. |
hide({ clear_history }), show() |
Hides the channel from the user's channel lists until a new message arrives, or shows it again. data.hidden changes on channel.hidden and channel.visible. |
Members
| Method | What it does |
|---|---|
addMembers({ members, ...options }) |
Adds members by user id or as ChannelMemberRequest objects. members changes on member.added. |
removeMembers({ members, ...options }) |
Removes members. members changes on member.removed. |
inviteMembers({ members, ...options }) |
Invites users. |
acceptInvite(options), rejectInvite(options) |
Accepts or rejects the current user's invitation. |
banUser(userId, options), unbanUser(userId, options) |
Bans or unbans a user in this channel. |
queryMembers({ payload }) |
A one-off query of members. Changes no state. |
message and options are optional, except that Channel.banUser() needs options, even if it's {}. message is a system message to send with the change.
Channel data
| Method | What it does |
|---|---|
update(request) |
Replaces the channel's data, for example { data: { name } }. Applies the response to data at once. |
updatePartial({ set, unset }) |
Changes some fields. Applies the response to data at once, and dispatches capabilities.changed when own_capabilities changed. |
enableSlowMode(seconds), disableSlowMode() |
Sets data.cooldown, through update(). |
addFilterTags({ tags }), removeFilterTags({ tags }) |
Changes the channel's filter tags, through update(). |
truncate(request) |
Removes the channel's messages, or those before truncated_at. The lists change on channel.truncated. |
delete({ hard_delete }) |
Deletes the channel. On channel.deleted, the client disposes the instance. |
Read state
| Method | What it does |
|---|---|
markRead(request) |
Sends the mark read request directly. Throws before the channel is loaded, or when config.readEvents.enabled is false. read changes on message.read. |
markReadViaReporter(request) |
Marks the channel read through client.messageDeliveryReporter. Uses requestHandlers.markReadRequest when registered, and markReadLocally() while read events are off and localUnreadCountEnabled is on. |
markReadLocally() |
Resets the current user's unread count without a request, by dispatching message.read_locally. Returns the event, or undefined without a user. |
markUnread(request) |
Marks the channel unread from request.message_id or request.message_timestamp. Throws like markRead(). read changes on notification.mark_unread. |
countUnread(lastRead) |
Counts the newest loaded messages after lastRead that count as unread. Without lastRead, returns state.unreadCount. |
countUnreadMentions() |
Counts the newest loaded unread messages that mention the current user. |
lastRead() |
The current user's last_read, null if never read, or undefined without a user. |
Typing, events and AI
| Method | What it does |
|---|---|
keystroke(parentId) |
Sends typing.start, at most every 2 seconds. Pass a parent message id for a thread. Does nothing while typing events are off. |
stopTyping(parentId) |
Sends typing.stop. |
sendEvent({ event }) |
Sends a custom event to the channel. |
updateAIState(messageId, state, options) |
Sends ai_indicator.update for a message. Usually called by the server that runs the AI. |
clearAIIndicator(), stopAIResponse() |
Send ai_indicator.clear and ai_indicator.stop. |
Requests that change no state
These send the request and return the response. Nothing in the channel's state changes until an event arrives:
Channel.sendMessage(request),Channel.sendReaction(...)andChannel.deleteReaction(...). Usechannel.messageOperations, which also updates the message list.Channel.getPinnedMessages(request),Channel.getMessagesById(ids),Channel.queryChannelBans(request)andChannel.getDraft(request).Channel.uploadFile(request),Channel.uploadImage(request),Channel.deleteFile({ url })andChannel.deleteImage({ url }).Channel.vote(...)andChannel.removeVote(...)for polls, andChannel.sendSharedLocation(location)andChannel.stopLiveLocationSharing(location)for locations.
Events
Channel.on(type, handler) and Channel.on(handler) subscribe to the channel's events, and return { unsubscribe }. Channel.off(type, handler) removes a handler. The client applies an event to the channel's state before any handler runs, so a handler reads the updated state. A disposed channel's state no longer changes, but its handlers still run.
const { unsubscribe } = channel.on("member.added", (event) => {
// channel.state already holds the new member
console.log(`${event.member?.user?.name} joined`, channel.state.members);
});The channel changes its state on these events:
| Event | What changes |
|---|---|
message.new |
The message list and the pinned list. Each unread_messages in read grows, except the sender's, which is reset to 0. The current user's grows only for a message that counts as unread, and the current user's own message changes no count. A hidden channel becomes visible. A thread-only reply changes neither list nor count. |
message.updated, message.undeleted, message.deleted, user.messages.deleted |
The message list and the pinned list. message.updated and message.undeleted skip replies. |
reaction.new, reaction.updated, reaction.deleted |
The message, in every list that shows it. |
message.read, notification.mark_read, message.read_locally |
The reader's entry in read. A thread read changes nothing here. |
notification.mark_unread |
The current user's entry in read, and the message list's unread snapshot. |
message.delivered |
The delivery fields of the user's entry in read. |
typing.start, typing.stop |
typing. An entry older than 7 seconds is removed, and the client dispatches typing.stop for it. |
member.added, member.updated |
members, and membership for the current user. |
member.removed |
members. |
user.banned, user.unbanned |
The member's banned and shadow_banned. |
user.watching.start, user.watching.stop |
watchers. |
channel.updated |
data, memberCount and ownCapabilities. When frozen changes, the channel queries itself again to refresh ownCapabilities. |
channel.truncated |
data, the message list, the pinned list and the current user's unread count. |
channel.hidden, channel.visible |
data.hidden. With clear_history, channel.hidden also empties the message list and the pinned list. |
ai_indicator.update, ai_indicator.clear, ai_indicator.stop |
aiState. |
Any event with watcher_count |
watcherCount. |
These are handled by the client, for every channel it stores:
| Event | What changes |
|---|---|
notification.channel_mutes_updated, health.check, connection.ok |
muteStatus. |
notification.mark_read with unread_channels: 0 |
The current user's unread count, in every channel. |
user.updated, user.presence.changed |
The user's object in members, membership and watchers, and in read for user.updated. |
user.deleted that deletes the user's messages, user.messages.deleted without a cid |
That user's messages in the message list and the pinned list. |
channel.deleted, notification.channel_deleted, notification.removed_from_channel |
The channel is disposed: pendingDisposal becomes true. |
The client also dispatches events of its own. capabilities.changed fires when a query or Channel.updatePartial() returns different capabilities, and channels.queried fires after every query.
user.watching.start, user.watching.stop, user.banned and user.unbanned change watchers and members in place, without a new object. So does a query that loads watchers. A subscription that selects watchers or members misses the change until another update replaces the object. member.removed doesn't clear membership when the current user is removed.
See also
- What channels and threads hold explains the channel's state.
- Shared entities and Entity lifecycle explain getting, loading and releasing channels.
- Thread and thread manager lists the API of threads.