From 6cbe60c0cfe90178b095de2ce6c7bbbc6f91d661 Mon Sep 17 00:00:00 2001 From: Oliver Lazoroski Date: Fri, 6 Nov 2026 13:02:41 +0100 Subject: [PATCH 1/2] docs(ai-migration): custom fields live under custom, and sendMessage takes a message The v14 -> v15 guide covered channel name and image moving under custom, but not the app's own fields on messages, users and members, nor stream-chat v10's channel.sendMessage({ message }) shape. Agents writing v15 code from the guide put custom fields at the top level and called sendMessage({ text, custom }). Add a Quick reference row, a row in "Breaks that do not fail to compile", and a section 17 bullet that links stream-chat-js' custom-data entry. --- ai-docs/ai-migration-v14-v15.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ai-docs/ai-migration-v14-v15.md b/ai-docs/ai-migration-v14-v15.md index 47c6ae538..ad1fa3662 100644 --- a/ai-docs/ai-migration-v14-v15.md +++ b/ai-docs/ai-migration-v14-v15.md @@ -266,6 +266,7 @@ Old → new for the most common changes. Ctrl-F the v14 name; the section has th | `client.setMessageComposerSetupFunction(fn)` | `client.config.setSetupFunction('messageComposer', fn)` | [13](#setmessagecomposersetupfunction-is-deprecated) | | `new Date(message.created_at)`, `dayjs(created_at)` | `convertTimestampToDate(created_at)` from `stream-chat` (may return `undefined`) | [16](#16-dates-on-response-types-are-unix-nanosecond-numbers) | | `channel.data?.name` / `.image` | `channel.data?.custom?.name` / `.image` | [17](#17-other-stream-chat-v10-data-shapes) | +| `channel.sendMessage({ text, priority })`, `message.priority`, `priority` set in `doSendMessageRequest` | `channel.sendMessage({ message: { text, custom: { priority } } })`; read `message.custom?.priority`; augment `CustomMessageData` | [17](#17-other-stream-chat-v10-data-shapes) | | `handleDelete({ deleteForMe, hardDelete })` | `handleDelete({ delete_for_me, hard })` | [17](#17-other-stream-chat-v10-data-shapes) | | `DefaultCommandData` import, `declare module 'stream-chat' { interface CustomCommandData … }` | Delete both; `Command['name']` is `string` | [17](#17-other-stream-chat-v10-data-shapes) | | `client.flagMessage(id)` / `channel.addMembers(ids)` / `client.getThread(id, options)` | `client.moderation.flagMessage({ message_id })` / `channel.addMembers({ members: ids })` / `client.getThreadAndHydrate({ message_id }, { watch: true })` | [17](#17-other-stream-chat-v10-data-shapes) | @@ -291,6 +292,7 @@ These compile cleanly, or are ignored in plain JS, and change behavior at runtim | A thread renders below the channel instead of beside it, or a panel collapses to a narrow column | The channel no longer contains a flex row, and `ChatView` slots are `flex: 1 1 0` siblings | Make your container a row; size panels by slot ([section 19](#panel-layout)) | ``, the channel list or thread list fills the view, the channel or thread beside it is not visible and the page scrolls horizontally | `--str-chat__channel-list-width` and `--str-chat__thread-list-width` default to `100%`, and the lists do not shrink | Set both widths on the `ChatView` root ([section 19](#panel-layout)) | `views=` with `ChannelNavigation` / `ChannelList` / `ThreadList`, and no `--str-chat__channel-list-width` (`styles`) | | A channel name or image renders empty | `name` / `image` are custom data in `stream-chat` v10: `channel.data.custom.name` | Read `custom` ([section 17](#17-other-stream-chat-v10-data-shapes)) | `.data?.name`, `.data.name`, `.data?.image` | +| A field of the app's own reads `undefined` on a message, user or member, or a field set on an outgoing message is not where the app reads it back | Custom fields are under `custom`, on reads and writes. TypeScript rejects a top-level field only in an inline object literal | Read and write them under `custom` ([section 17](#17-other-stream-chat-v10-data-shapes)) | Reads and writes of the app's own fields; `sendMessage(`; `sendMessageRequest`, `updateMessageRequest` | | A held channel throws "pending disposal" after logout or a user switch | `disconnectUser()` disposes the user's channels | Drop held channels on disconnect ([section 4](#without-chatview)) | `disconnectUser`, `useState`. - `MessageSearchResultItem` / `ChannelByMessageSearchResultItemProps.item`: `MessageResponse & { channel?: ChannelResponse }`. +- **Your own fields are custom data on every object.** In `stream-chat` v10 the fields an app adds to a message, user, member, reaction, attachment or event live in that object's `custom`, not at the top level, both in what the server returns and in what the app sends. Read `message.custom?.priority`, not `message.priority`. Send them wrapped in the request's `message`: `channel.sendMessage({ message: { text, custom: { priority } } })` (`stream-chat` v9's `channel.sendMessage({ text })` no longer takes the message at the top level), and in a send handler as `{ ...params, message: { ...params.message, custom: { ...params.message?.custom, priority } } }` ([section 14](#14-request-handlers)). Type the fields by augmenting `CustomMessageData` (and the other `Custom*Data` interfaces), not by widening `MessageRequest` or `LocalMessage`. Filters on them use `'custom.'`. `updateUsersPartial` and the other partial updates keep the flat shape. The full rules are under [Custom data moves under `custom`](https://github.com/GetStream/stream-chat-js/blob/release-v10/ai-docs/v10-breaking-changes.md#custom-data) in stream-chat-js. + **Messages:** - **Reaction sort:** `reactionDetailsSort` (on `Message`, `MessageList`, `VirtualizedMessageList`, `MessageContext`, `MessageReactions`, `MessageReactionsDetail`, `VirtuosoContext`), `MessageReactionsDetail.sort`, and the `sort` argument of `handleFetchReactions` / `useReactionsFetcher`: `ReactionSort` → `SortParamRequest[]` (`[{ field: 'created_at', direction: -1 }]`). From e8a5fe5977c859893271d3807a905b50d2832849 Mon Sep 17 00:00:00 2001 From: Oliver Lazoroski Date: Fri, 6 Nov 2026 13:19:04 +0100 Subject: [PATCH 2/2] docs(ai-migration): partial updates keep flat keys, and retries skip send-handler fields Address review: the breaks-that-compile row now excludes updateUsersPartial and the other partial updates, and the section 17 bullet notes that a registered retrySendMessageRequest resends the payload cached before sendMessageRequest changed it. --- ai-docs/ai-migration-v14-v15.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ai-docs/ai-migration-v14-v15.md b/ai-docs/ai-migration-v14-v15.md index ad1fa3662..d4382bfc7 100644 --- a/ai-docs/ai-migration-v14-v15.md +++ b/ai-docs/ai-migration-v14-v15.md @@ -292,7 +292,7 @@ These compile cleanly, or are ignored in plain JS, and change behavior at runtim | A thread renders below the channel instead of beside it, or a panel collapses to a narrow column | The channel no longer contains a flex row, and `ChatView` slots are `flex: 1 1 0` siblings | Make your container a row; size panels by slot ([section 19](#panel-layout)) | ``, the channel list or thread list fills the view, the channel or thread beside it is not visible and the page scrolls horizontally | `--str-chat__channel-list-width` and `--str-chat__thread-list-width` default to `100%`, and the lists do not shrink | Set both widths on the `ChatView` root ([section 19](#panel-layout)) | `views=` with `ChannelNavigation` / `ChannelList` / `ThreadList`, and no `--str-chat__channel-list-width` (`styles`) | | A channel name or image renders empty | `name` / `image` are custom data in `stream-chat` v10: `channel.data.custom.name` | Read `custom` ([section 17](#17-other-stream-chat-v10-data-shapes)) | `.data?.name`, `.data.name`, `.data?.image` | -| A field of the app's own reads `undefined` on a message, user or member, or a field set on an outgoing message is not where the app reads it back | Custom fields are under `custom`, on reads and writes. TypeScript rejects a top-level field only in an inline object literal | Read and write them under `custom` ([section 17](#17-other-stream-chat-v10-data-shapes)) | Reads and writes of the app's own fields; `sendMessage(`; `sendMessageRequest`, `updateMessageRequest` | +| A field of the app's own reads `undefined` on a message, user or member, or a field set on an outgoing message is not where the app reads it back | Custom fields are under `custom`, on reads and writes. TypeScript rejects a top-level field only in an inline object literal | Read and write them under `custom` ([section 17](#17-other-stream-chat-v10-data-shapes)); `updateUsersPartial` and the other partial updates keep flat keys in `set` / `unset` | Reads and writes of the app's own fields; `sendMessage(`; `sendMessageRequest`, `updateMessageRequest` | | A held channel throws "pending disposal" after logout or a user switch | `disconnectUser()` disposes the user's channels | Drop held channels on disconnect ([section 4](#without-chatview)) | `disconnectUser`, `useState`. - `MessageSearchResultItem` / `ChannelByMessageSearchResultItemProps.item`: `MessageResponse & { channel?: ChannelResponse }`. -- **Your own fields are custom data on every object.** In `stream-chat` v10 the fields an app adds to a message, user, member, reaction, attachment or event live in that object's `custom`, not at the top level, both in what the server returns and in what the app sends. Read `message.custom?.priority`, not `message.priority`. Send them wrapped in the request's `message`: `channel.sendMessage({ message: { text, custom: { priority } } })` (`stream-chat` v9's `channel.sendMessage({ text })` no longer takes the message at the top level), and in a send handler as `{ ...params, message: { ...params.message, custom: { ...params.message?.custom, priority } } }` ([section 14](#14-request-handlers)). Type the fields by augmenting `CustomMessageData` (and the other `Custom*Data` interfaces), not by widening `MessageRequest` or `LocalMessage`. Filters on them use `'custom.'`. `updateUsersPartial` and the other partial updates keep the flat shape. The full rules are under [Custom data moves under `custom`](https://github.com/GetStream/stream-chat-js/blob/release-v10/ai-docs/v10-breaking-changes.md#custom-data) in stream-chat-js. +- **Your own fields are custom data on every object.** In `stream-chat` v10 the fields an app adds to a message, user, member, reaction, attachment or event live in that object's `custom`, not at the top level, both in what the server returns and in what the app sends. Read `message.custom?.priority`, not `message.priority`. Send them wrapped in the request's `message`: `channel.sendMessage({ message: { text, custom: { priority } } })` (`stream-chat` v9's `channel.sendMessage({ text })` no longer takes the message at the top level), and in a send handler as `{ ...params, message: { ...params.message, custom: { ...params.message?.custom, priority } } }` ([section 14](#14-request-handlers)). A retry resends the payload cached before any handler changed it, through `retrySendMessageRequest` when one is registered, so set the field in that handler too, or set it on the message before sending. Type the fields by augmenting `CustomMessageData` (and the other `Custom*Data` interfaces), not by widening `MessageRequest` or `LocalMessage`. Filters on them use `'custom.'`. `updateUsersPartial` and the other partial updates keep the flat shape. The full rules are under [Custom data moves under `custom`](https://github.com/GetStream/stream-chat-js/blob/release-v10/ai-docs/v10-breaking-changes.md#custom-data) in stream-chat-js. **Messages:**