Upgrade to v8

This page lists what changed between v7 and v8, and what to change in your app.

v8 is built on stream-chat (the JavaScript client) v10. Most of the SDK's own API (components, services, templates) is unchanged, but some types and behaviors changed because stream-chat v10 changed its data model. If your app calls the stream-chat client directly (chatService.chatClient, channel.*), the most common client changes are summarized in Using the stream-chat client directly. The complete list is stream-chat v9 to v10 breaking changes in the stream-chat repository.

Quick migration checklist

  1. Upgrade both packages: npm install stream-chat-angular stream-chat. See Installation and requirements.
  2. Stream API v2 changes: all response dates are TimestampNS, and all custom data is sent in the custom wrapper. See Stream API v2 changes.
  3. Move client options under client in ChatClientService.init. See Client initialization.
  4. Pass the channel list sort as an array: [{ field: "last_message_at", direction: -1 }]. See Sort.
  5. Prefix custom fields in channel filters with custom.. See Channel list.
  6. Read custom data from custom: a channel's name is channel.data?.custom?.name. See Custom data is under custom.
  7. Replace StreamMessage with LocalMessage, and convert message dates with nsToDate: messages are stream-chat's LocalMessage objects, their dates are TimestampNS, not Date, and readBy, translation and errorStatusCode moved out of the message. See StreamMessage was removed.
  8. Switch custom channel queries to queryChannelsAndHydrate. See Custom channel queries.
  9. Stop reading messages from channel.state. Use the SDK's observables or the channel's message paginator. See Message list and pagination.
  10. Add custom: {} to attachments you create, and read attachment metadata from attachment.custom. See Attachments and voice recordings.
  11. Replace connection.changed listeners with connectionState$. See Connection state.
  12. Check bounced and blocked messages with message.moderation instead of moderation_details. See Moderation.
  13. Add the new translation key if you provide your own translations. See Translations.
  14. Update error handling for StreamAPIError: it replaces ErrorFromResponse, and its message no longer has the StreamChat error code prefix. See Error handling.
  15. Pass channel and translation through custom templates: if a custom message template displays stream-message, pass the channel of the template context. If a custom message text template displays stream-message-text, pass the translation. See StreamMessage was removed.
  16. Check failed edits on the message: updateMessage doesn't reject if the request fails, failed edits are displayed on the message. See Sending, editing and deleting messages.

Installation and requirements

Install v8 together with stream-chat v10:

npm install stream-chat-angular stream-chat
  • The stream-chat peer dependency changed from ^9.20.0 to ^10.0.0.
  • stream-chat v10 requires Node.js 22.18.0 or later for installation and builds.
  • The stream-chat v10 type definitions work with TypeScript 5.2 or later (the minimum supported by Angular 17).
  • Angular 17 to 22 are still supported, see Angular version support.
  • The CSS (@stream-io/stream-chat-css) and theming didn't change.

Stream API v2 changes

stream-chat v10 uses v2 of the Stream API. Two changes affect almost every part of your app:

  • All response dates are TimestampNS. Date fields in API responses (and in the state built from them) are unix nanoseconds, a branded number, instead of Date objects or ISO strings. Convert them with the nsToDate helper exported by stream-chat when you need a Date. See StreamMessage was removed.
  • All custom data is sent in the custom wrapper. Custom fields of channels, messages, users, attachments, reactions, polls and events are no longer top-level fields: they are sent and returned under the entity's custom object. This applies to channel filters as well (custom. prefix). See Custom data is under custom and Filters.

Client initialization

Client options

The last parameter of ChatClientService.init is now the stream-chat v10 configuration tree (DeepPartial<InstanceConfigTree>). Client options moved under the client key, and axios options under client.axiosRequestConfig. trackPendingChannelInvites stays a top-level option.

In v7:

chatService.init(apiKey, userId, token, {
  timeout: 10000,
  trackPendingChannelInvites: true,
});

In v8:

chatService.init(apiKey, userId, token, {
  client: { axiosRequestConfig: { timeout: 10000 } },
  trackPendingChannelInvites: true,
});

The StreamChat options mapping lists where every option moved.

User parameter

The type of the userOrId parameter changed from string | OwnUserResponse | UserResponse to string | ClientUser. ClientUser is exported by stream-chat: it's a partial OwnUserResponse with a required id. You can pass { id, name, image } objects as before. Custom user fields go under custom, see Custom data is under custom.

State recovery after reconnect

The SDK no longer sets recoverStateOnReconnect (the option doesn't exist in stream-chat v10). Connection recovery is on by default. After a reconnect, the SDK refreshes the channel list as before, and stream-chat reloads the active channel and the open thread.

If you turn connection recovery off (client.connectionRecovery.enabled: false), stream-chat still dispatches the connection.recovered event after every reconnect, so the SDK still refreshes the channel list. But stream-chat doesn't reload the active channel and the open thread, so they can miss the messages sent while the user was offline.

Connection state

stream-chat v10 no longer emits the connection.changed event. ChatClientService.connectionState$ ('online' | 'offline') and the "Connection failure, reconnecting now..." notification still work, they are now based on the WebSocket's health.

If you listened to connection.changed on ChatClientService.events$ or with chatClient.on, use connectionState$ instead (or chatClient.wsConnection.state if you need the raw WebSocket health):

chatService.connectionState$.subscribe((state) => {
  console.log(`Chat is ${state}`);
});

Channel list

Filters

ChannelFilters is now the stream-chat v10 type. Custom fields need the custom. prefix, and each key allows only one operator (combine more with $and).

In v7:

channelService.init({ type: "messaging", color: "green" });

In v8:

channelService.init({ type: "messaging", "custom.color": "green" });

Sort

The sort parameter of ChannelService.init (and the ChannelQuery constructor) takes the stream-chat v10 sort format: an array of { field, direction } objects (SortParamRequest[] from stream-chat). The object format ({ last_message_at: -1 }) is no longer accepted, and the ChannelSort type no longer exists.

In v7:

channelService.init(filters, { last_message_at: -1 });

In v8:

import { SortParamRequest } from "stream-chat";

const sort: SortParamRequest[] = [{ field: "last_message_at", direction: -1 }];
channelService.init(filters, sort);

The order of the array sets the sort priority, the same way the key order of the object format did.

Custom channel queries

In stream-chat v10, client.queryChannels returns the raw API response and doesn't create Channel instances. If your initWithCustomQuery function called queryChannels, switch to queryChannelsAndHydrate. Filter, sort and options move into one request object.

In v7:

channelService.initWithCustomQuery(async () => {
  const channels = await client.queryChannels(
    filters,
    { last_message_at: -1 },
    { limit: 25 },
  );
  return { channels, hasMorePage: channels.length === 25 };
});

In v8:

channelService.initWithCustomQuery(async () => {
  const channels = await client.queryChannelsAndHydrate({
    filter_conditions: filters,
    sort: [{ field: "last_message_at", direction: -1 }],
    limit: 25,
  });
  return { channels, hasMorePage: channels.length === 25 };
});

The Channel list state page explains how the channel list works.

Custom data is under custom

In stream-chat v10, every custom field lives under the custom object of its entity (channel, message, user, attachment, reaction, poll, event). Update the templates and code that read or write custom data.

Channel name and image

name and image are custom channel fields, so they moved too.

In v7:

{{ channel.data?.name }} <img [src]="channel.data?.image" />

In v8:

{{ channel.data?.custom?.name }} <img [src]="channel.data?.custom?.image" />

The SDK's DefaultChannelData type now types channel.data.custom.name and channel.data.custom.image. If you extend CustomChannelData (see Custom types), your fields are typed under channel.data.custom as well.

stream-chat v10 removed CustomCommandData, and command names are plain string. Delete its augmentations and imports. A leftover declare module "stream-chat" augmentation still compiles but has no effect, so search your code for the name.

Messages

Custom message fields are read from message.custom (for example message.custom.priority). The customData parameter of ChannelService.sendMessage is sent as custom, you don't need to change how you call it. The customData returned by beforeSendMessage works the same way.

Users, reactions and events

  • Custom user fields are under user.custom. name and image stay top-level user fields.
  • The customData parameter of ChannelService.addReaction is sent as reaction.custom.
  • Custom event fields are under event.custom.

StreamMessage was removed

StreamMessage was removed. The SDK displays messages as stream-chat stores them: every message (activeChannelMessages$, activeThreadMessages$, activeParentMessage$, messageToQuote$, component inputs and template contexts) is a stream-chat v10 LocalMessage, without any SDK-specific field. The SDK emits the same objects that stream-chat holds in the channel's message paginator (channel.messagePaginator). Replace StreamMessage with LocalMessage (imported from stream-chat) in your code. The Message page describes the message type.

The fields StreamMessage added to the message are available elsewhere:

v7 StreamMessage field v8
readBy ReadStatusContext.readBy in a custom read status template, or MessageComponent.readBy. Outside the message component use getReadBy(message, channel), or watchReadBy(message, channel, callback) to be notified when the read state changes
translation, quoted_message.translation MessageTextContext.translation in a custom message text template, or MessageTextComponent.translation. Outside of templates use getMessageTranslation(message, language, currentUserId) with ChannelService.activeChannelTranslationLanguage$
errorStatusCode message.error?.status (error is the error of the failed request, set by stream-chat)

The message component now reads the read state and the translation language itself, so it needs the channel of the message, and the message text component needs the translation:

  • If a custom message template (CustomTemplatesService.messageTemplate$) displays stream-message, pass the new channel field of MessageContext to its channel input. Without it, read receipts aren't displayed.
  • If a custom message text template (CustomTemplatesService.messageTextTemplate$) displays stream-message-text, pass the new translation field of MessageTextContext to its translation input. Without it, the original text is displayed even if the user selects the translation.
<ng-template
  #messageText
  let-message="message"
  let-isQuoted="isQuoted"
  let-shouldTranslate="shouldTranslate"
  let-translation="translation"
>
  <stream-message-text
    [message]="message"
    [isQuoted]="isQuoted"
    [shouldTranslate]="shouldTranslate"
    [translation]="translation"
  ></stream-message-text>
</ng-template>

Dates

The SDK doesn't convert the date fields of messages: like everywhere in stream-chat v10, they are TimestampNS values (unix nanoseconds, a branded number). Convert them with the nsToDate helper exported by stream-chat when you need a Date.

In v7:

const sentAt = message.created_at.toLocaleString();

In v8:

import { nsToDate } from "stream-chat";

const sentAt = nsToDate(message.created_at).toLocaleString();

Angular's date pipe treats numbers as milliseconds, so convert the date in your component (or with your own pipe) before passing it to the pipe.

Change v7 v8
Type StreamMessage (FormatMessageResponse with SDK fields) LocalMessage
Date fields (created_at, updated_at, deleted_at, pinned_at, pin_expires, message_text_updated_at) Date (or null) TimestampNS (unix nanoseconds), optional fields are undefined, not null
quoted_message MessageResponseBase & { translation } LocalMessage (dates are TimestampNS, no translation)
readBy, translation, errorStatusCode set by the SDK removed, see the table above
Custom fields top-level under custom
moderation_details moderation result (v1) removed, use moderation (see Moderation)
__html set on message previews removed

DateParserService and custom date parsers still receive Date objects: the SDK converts the message dates before calling them. The date separator template context (DateSeparatorContext.date) is also a Date.

stream-chat no longer exports these message types: FormatMessageResponse (use LocalMessage), MessageResponseBase (use MessageResponse), DraftMessagePayload (use MessageRequest) and CommandResponse (use Command).

Message list and pagination

stream-chat v10 removed channel.state.messages, latestMessages, messageSets, threads, pinnedMessages and loadMessageIntoState. Messages now live in channel.messagePaginator, pinned messages in channel.pinnedMessagesPaginator and thread replies in thread.messagePaginator. The SDK's observables (activeChannelMessages$, activeThreadMessages$, activeChannelPinnedMessages$) work as before, but code that read the channel state directly has to change:

v7 v8
channelService.activeChannel.state.messages channelService.activeChannelMessages$ or channel.messagePaginator.items
channel.state.latestMessages channel.messagePaginator.anchoredHeadItems (latest loaded messages), channel.messagePaginator.latestMessage (latest message)
channel.state.pinnedMessages channelService.activeChannelPinnedMessages$ or channel.pinnedMessagesPaginator.items
channel.state.threads[parentId] channelService.activeThreadMessages$ or client.threads.get(parentId)?.messagePaginator.items
channel.state.loadMessageIntoState(id) channelService.jumpToMessage(id)
channel.lastMessage() channel.messagePaginator.latestMessage
channel.state.messages = [] / addMessageSorted / removeMessage channel.messagePaginator.removeItem({ id }) / ingestItem(message) (v10 LocalMessage)

loadMoreMessages and loadMoreThreadReplies

  • The return type of loadMoreMessages changed from false | Promise<QueryChannelAPIResponse> to false | Promise<void>. loadMoreThreadReplies still returns false | Promise<void>.
  • Both return false (no request) if there's nothing more to load in the given direction, a page is already loading, or no message is loaded yet. If the request fails, the returned promise rejects.
  • loadMoreThreadReplies('newer') now loads newer replies (in v7 it always returned false). This happens after jumping to an older reply.

jumpToMessage

The signature of jumpToMessage is unchanged, but it now uses the message paginator of stream-chat v10:

  • If the message isn't found or can't be loaded, the promise rejects and the "Message not found" notification is displayed (same as before).
  • Jumping to a thread reply also loads the parent message into the channel's message list if needed, then opens the thread and jumps to the reply.

Channel preview

The type of ChannelPreviewComponent's latestMessage changed from FormatMessageResponse to LocalMessage, and the latestMessage field of the channel preview info template context (ChannelPreviewInfoContext) changed from StreamMessage to LocalMessage. Its dates are TimestampNS, and it doesn't have the readBy and translation fields. The latest message now comes from channel.messagePaginator, so custom channel preview templates no longer need message.new, message.updated and message.deleted listeners to stay up to date.

Threads

  • In stream-chat v10, channels don't store thread replies. Replies are stored by Thread instances held by client.threads. The SDK creates the Thread (client.threads.ensure) when you call setAsActiveParentMessage.
  • channel.getReplies was removed in v10, and the SDK no longer calls it. If your code did, use client.threads.get(parentId)?.messagePaginator, or the raw client.getReplies({ parent_id }, options) request.
  • The second parameter of setAsActiveParentMessage (loadMessagesFrom) is deprecated and ignored: replies are always loaded by the thread.
  • The SDK calls client.threads.registerSubscriptions() in ChannelService.init, threads need it to receive WebSocket events. Don't call client.threads.unregisterSubscriptions(), it disposes the reply lists of the threads.

Custom event handlers and setters

The custom event handlers of ChannelService (customNewMessageHandler, customChannelUpdatedHandler, customChannelTruncatedHandler, customChannelDeletedHandler, customChannelHiddenHandler, customChannelVisibleHandler, customNewMessageNotificationHandler, customAddedToChannelNotificationHandler and customRemovedFromChannelNotificationHandler) keep their signatures, with these changes:

  • The event parameter (and ClientEvent.event) is the v10 Event type. It's a union of the event types: a field only exists on the events that have it, so narrow by event.type first:

    if (event.type === "notification.message_new") {
      const channel = event.channel;
    }

    stream-chat renamed EventTypes to EventType.

  • messageListSetter, threadListSetter and parentMessageSetter take LocalMessage instead of StreamMessage.

  • messageListSetter and threadListSetter still work, but the message lists are owned by the message paginators of stream-chat: the next paginator update (for example the next message) overwrites the list you set. To permanently change a message list, update the paginator instead (channel.messagePaginator.removeItem / ingestItem).

  • channel.updated: the SDK no longer copies event.channel into channel.data, stream-chat v10 does it (keeping hidden, own_capabilities and member_count).

  • channel.truncated: the SDK no longer clears channel.state.messages, stream-chat v10 truncates the message list.

  • The SDK no longer handles message.updated, message.deleted and reaction.* events itself: the message lists are updated from the message paginators.

Attachments and voice recordings

In stream-chat v10, attachment metadata moved under attachment.custom. This includes the fields added by the SDK:

v7 v8
attachment.mime_type attachment.custom.mime_type
attachment.file_size attachment.custom.file_size
attachment.duration attachment.custom.duration
attachment.waveform_data attachment.custom.waveform_data
attachment.img_url (deprecated) attachment.custom.img_url (deprecated)
attachment.isCustomAttachment attachment.custom.isCustomAttachment
  • Every attachment needs a custom object (it can be empty). Add custom: {} to the attachments you create, for example with AttachmentService.addAttachment:

    attachmentService.addAttachment({
      type: "image",
      image_url: "https://example.com/image.png",
      custom: {},
    });
  • AttachmentUpload.extraData is merged into the attachment, so put custom metadata in extraData.custom.

  • GalleryAttachment (the gallery context of custom attachment templates) has a new, required custom property.

  • DefaultAttachmentData has two new fields: duration and waveform_data.

  • If you have custom attachment templates, read the fields from their new location.

  • Custom upload and delete handlers (customImageUploadRequest, customFileUploadRequest, customImageDeleteRequest, customFileDeleteRequest) are unchanged. The default upload requests now use channel.uploadImage / uploadFile (channel.sendImage / sendFile were removed in v10).

  • AttachmentListComponent compares the CDN links of attachments (image_url, thumb_url and asset_url) without their query string. Attachment URLs are signed, and some message updates (for example adding a reaction) renew the signature, so message.attachments can change even if the attachments are the same. AttachmentListComponent ignores these changes, so images and videos aren't reloaded. If you display attachments with your own component instead of stream-attachment-list, you may want to do the same.

Message input and drafts

  • MessageInputComponent.messageDraftChange emits MessageRequest | undefined (it was DraftMessagePayload | undefined, removed in v10). The fields are the same (text, attachments, mentioned_users, poll_id, parent_id, quoted_message_id).
  • MessageInputComponent.loadDraft takes the v10 DraftResponse: draft.message.mentioned_users is now a list of users (it was a list of ids), and dates are TimestampNS. To save a draft with stream-chat v10, call channel.createDraft({ message: draft }).
  • Slash commands come from channel.config.availableCommands (it was channel.getConfig().commands). ComandAutocompleteListItem is now based on Command (it was CommandResponse).

Sending, editing and deleting messages

ChannelService.sendMessage, resendMessage, updateMessage and deleteMessage keep their signatures, with LocalMessage instead of StreamMessage (the same for the beforeUpdateMessage and messageDeleteConfirmationHandler hooks). They now use the message operations of stream-chat (channel.messageOperations and thread.messageOperations):

  • Edits are displayed immediately. If the update request fails, the message is marked as failed (message.status === 'failed', the error is in message.error), and the user can retry by clicking the message. The "Edit message request failed" notification was removed, and updateMessage doesn't reject if the request fails: check the status and error of the returned message instead.
  • updateMessage sends the whole message (the same as in v7). beforeUpdateMessage may receive a message with i18n (the translations of the text): stream-chat clears it only if the text of the message changes.
  • Deletes are displayed immediately. If the delete request fails, the message is restored, and deleteMessage rejects (the "Error deleting message" notification is displayed, the same as in v7).
  • Sends that reached the API: if a send request fails, but the API saved the message (for example the response was lost), the message is displayed as sent. In v7 this was only handled when resending a message.

Polls

  • stream-chat v10 renamed the poll types, and the SDK's poll components use the new names. This matters for custom poll templates and if you use the poll components directly:

    v7 v8
    PollOption PollOptionResponseData
    PollVote, PollAnswer PollVoteResponseData

    Affected inputs: PollVoteComponent.vote, UpsertAnswerComponent.answer, PollVoteResultsListComponent.option and PollOptionSelectorComponent.option.

  • VotingVisibility is now a string union type ('anonymous' | 'public'), not an enum.

  • Poll, option, vote and answer dates are TimestampNS (unix nanoseconds), convert them with nsToDate.

  • Sort options of poll queries use the array format, for example [{ field: 'created_at', direction: -1 }].

Reactions

Moderation

stream-chat v10 messages no longer have moderation_details, use message.moderation instead:

  • Bounced messages: message.type === 'error' && message.moderation?.action === 'bounce'.
  • Blocked messages: message.type === 'error' && message.moderation?.action === 'remove'.

If your custom message template or MessageBouncePromptComponent customization checked moderation_details, switch to moderation.

Other type changes

Type / API v7 v8
ChatClientService.appSettings$ / AppSettings AppSettings from stream-chat AppSettings from stream-chat-angular (Partial<AppResponseFields>)
ChannelService.activeChannelMessages$, activeThreadMessages$, activeChannelPinnedMessages$ StreamMessage[] LocalMessage[]
ChannelService.activeChannelMessages getter (StreamMessage | MessageResponse | FormatMessageResponse)[] LocalMessage[]
ChannelService.activeParentMessage$, messageToQuote$, bouncedMessage$ StreamMessage LocalMessage
ChannelService.selectMessageToQuote StreamMessage | MessageResponse | undefined LocalMessage | MessageResponse | undefined
ChannelService.updateMessage, resendMessage, deleteMessage, beforeUpdateMessage, messageDeleteConfirmationHandler StreamMessage LocalMessage
MessageContext.message, MessageBlockedContext.message, ReadStatusContext.message, DeliveredStatusContext.message, SendingStatusContext.message, CustomMetadataContext.message, custom message actions StreamMessage LocalMessage
MessageTextContext.message, MessageTextComponent.message StreamMessage | MessageResponseBase | undefined LocalMessage | MessageResponse | undefined
getMessageTranslation(message, channel, user) reads the language from channel and user getMessageTranslation(message, language, currentUserId)
MessageComponent.lastReadUser, isOnlyReadByMe, isReadByMultipleUsers public fields removed, use MessageComponent.readBy
ChannelSort from stream-chat removed, use SortParamRequest[] from stream-chat
getChannelDisplayText(channel, user) user: UserResponse user?: { id?: string }
listUsers(users) users: UserResponse[] users: { id: string; name?: string }[]
AvatarComponent.user, AvatarPlaceholderComponent.user, AvatarContext.user User UserResponse
getReadBy(message, channel) message: FormatMessageResponse message: Pick<MessageResponse, 'created_at' | 'user'>
createMessagePreview(...) returns MessageResponse returns LocalMessage (customData is set as custom)
MessageListComponent.parseDate parseDate(date: Date) parseDate(timestamp: TimestampNS)

Translations

There is a new translation key: streamChat.Error loading messages. It's displayed if the messages of a channel or thread can't be loaded. If you provide your own translations (see Translation), add this key.

The streamChat.Edit message request failed key was removed: failed edits are displayed on the message, see Sending, editing and deleting messages.

Error handling

When an API request fails, stream-chat v10 throws a StreamAPIError (exported by stream-chat). It replaces the ErrorFromResponse class of v9, which no longer exists: update your imports and instanceof checks.

Field v9 (ErrorFromResponse) v10 (StreamAPIError)
message StreamChat error code <code>: <server message> the server's error message, without the prefix
code Stream error code (null if there is none) Stream error code (undefined if there is none)
status HTTP status code HTTP status code
response the HTTP response the HTTP response
name ErrorFromResponse StreamAPIError(status: <status>, code: <code>)

Read the fields from the error object itself. The error's toJSON() only keeps message, stack, name, code and status, so JSON.stringify(error) (or JSON.parse(JSON.stringify(error))) drops response. If your code serializes errors to read response.data, switch to reading the error directly.

Errors that don't come from the Stream API (for example network errors, timeouts or HTTP errors without a Stream error body) are thrown as they are, so check the type before reading Stream specific fields:

import { StreamAPIError } from "stream-chat";

try {
  await channel.markUnread({ message_id: messageId });
} catch (error) {
  if (error instanceof StreamAPIError) {
    // HTTP status, Stream error code, server error message, raw HTTP response
    console.log(error.status, error.code, error.message, error.response?.data);
  } else {
    // network error, timeout, etc.
  }
}

The SDK's own error notifications work with these errors. The errorStatusCode of failed messages was removed: read message.error?.status instead (see StreamMessage was removed). message.error is a StreamAPIError for API errors, and the original error otherwise.

Behavior changes

These changes don't require code changes, but your app may behave differently:

  • Reconnect: after a reconnect, the SDK refreshes the channel list (same as before), and stream-chat reloads the active channel and the open thread. The open thread now stays open (in v7 it was closed because its replies weren't reloaded).
  • Message list updates: messages arriving via WebSocket can be displayed up to 500 ms later, because stream-chat v10 batches message list updates. Messages sent by the current user are displayed immediately.
  • Message objects: the SDK emits the message objects stream-chat stores. When a message changes (for example a reaction is added), only that message gets a new object in activeChannelMessages$. Read state and translation language changes don't create new message objects, the message component reads them itself.
  • Read receipts are only tracked for the last message sent by the current user (the only message that displays them).
  • Edits and deletes are displayed immediately, see Sending, editing and deleting messages.
  • Hard-deleted user messages: user.messages.deleted events with hard_delete remove the messages instead of displaying a "This message was deleted" placeholder.
  • Read events: the active channel is marked as read with channel.markReadViaReporter(). If read events are disabled for the channel, the SDK doesn't send read requests, and marking a message as unread fails with an error notification.
  • Typing events are only sent if typing events are enabled for the channel (channel.config.typingEvents.enabled).

New APIs

  • ChannelService.activeChannelMessagesPaginationState$ and activeThreadMessagesPaginationState$: the pagination state of the message lists (MessageListPaginationState: hasMoreOlder, hasMoreNewer, isLoading, error).
  • ChannelService.jumpToFirstUnreadMessage(): jumps to the first unread message of the active channel. It returns false if there is no unread message to jump to. If loading the message fails, it displays the "Message not found" notification and throws the error.
  • ChannelService.reloadActiveChannelMessages(): retries loading the messages of the active channel, for example after an error.
  • ChannelService.activeChannelTranslationLanguage$: the language the messages of the active channel are translated to (the user's language, or the channel's auto_translation_language). It's updated on user.updated and channel.updated events.
  • MessageComponent.channel input and MessageContext.channel: the channel of the message, used to display read receipts.
  • MessageComponent.readBy and ReadStatusContext.readBy: the users who read the message.
  • MessageTextComponent.translation input and MessageTextContext.translation: the translation of the message text.
  • watchReadBy(message, channel, callback): calls callback with the users who read the message every time the read state of the channel changes, and returns a function that stops watching.
  • getTranslationLanguage(channel, user): the language messages are translated to (the user's language, or the channel's auto_translation_language). The SDK updates message translations when either of them changes.
  • New exported types: MessageListPaginationState and AppSettings.

Using the stream-chat client directly

If you use chatService.chatClient or Channel objects directly, these are the stream-chat v10 changes you're most likely to meet. The stream-chat v10 breaking changes list all of them.

stream-chat v9 stream-chat v10
client.activeChannels[cid] client.channelManager.get(cid)
client.queryChannels(filters, sort, options) client.queryChannelsAndHydrate({ filter_conditions, sort: [{ field, direction }], ...options })
client.queryUsers(filters, sort, options) client.queryUsers({ payload: { filter_conditions, sort, ...options } })
client.flagMessage(id) client.moderation.flagMessage({ message_id: id })
client.updateMessage(message) client.updateMessage({ id }, { message })
client.deleteMessage(id, hard) client.deleteMessage({ id }, { hard })
client.pinMessage(message) / unpinMessage(message) client.pinMessage({ id }) / unpinMessage({ id })
client.queryReactions(id, filter, sort, options) client.queryReactions({ id }, { filter, sort, ...options })
client.on('connection.changed', ...) client.wsConnection.state.subscribeWithSelector(({ isHealthy }) => ({ isHealthy }), ...)
client.logger(...) chatLoggerSystem
channel.sendMessage(message) channel.sendMessage({ message })
channel.sendReaction(id, reaction) / deleteReaction(id, type) channel.sendReaction({ id }, { reaction }) / deleteReaction({ id, type })
channel.markRead() channel.markReadViaReporter() (markRead() is now the raw request)
channel.getConfig() channel.serverConfig (server config), channel.config (resolved config)
channel.sendImage / sendFile channel.uploadImage({ file }) / uploadFile({ file })
channel.queryMembers(filters, sort, options) channel.queryMembers({ payload: { filter_conditions, sort, ...options } })
channel.getReplies(parentId, options) client.threads.get(parentId)?.messagePaginator, or client.getReplies({ parent_id }, options)
channel.state.* message storage channel.messagePaginator (see Message list and pagination)
Date fields as string / Date TimestampNS (unix nanoseconds), convert with nsToDate / dateToNs
Custom fields at the top level under custom
Sort as { field: direction } [{ field, direction }]