# Upgrade stream-chat to v9

## List of Breaking Changes

### Considered Major

- removal of `StreamChatGenerics` (`DefaultGenerics` and `ExtendableGenerics` types too)
- custom property `name` has been removed from type `ChannelResponse`, see note in [Replacement - Module Augmentation](#replacement---module-augmentation) section
- removal of the `jsDelivr` (UMD) bundle

### Considered Minor

- type `InviteOptions` has been renamed to `UpdateChannelOptions`
- type `UpdateChannelOptions` has been renamed to `UpdateChannelTypeRequest`
- type `ThreadResponseCustomData` has been renamed to `CustomThreadType`
- type `MarkAllReadOptions` has been deleted in favour of type `MarkChannelsReadOptions`
- type `QueryFilter` no longer supports `$ne` and `$nin` operators
- type `ChannelMembership` has been deleted in favour of type `ChannelMemberResponse`
- function `formatMessage` (`utils.ts`) no longer returns `__html` property in the formatted message output

<admonition type="info">

Note that `$ne` and `$nin` operators are inefficient and cause performance issues. You'll need to refactor your queries removing all `$ne` or `$nin` operators. Feel free to contact our [Customer Support](https://getstream.io/contact/support/) to get help finding an appropriate alternative solution for your integration.

</admonition>

## Removal of StreamChatGenerics

The generic was initially introduced to our codebase as a mean of providing our integrators with a way to extend our types with their own type definitions. This was mostly targeted at custom data of certain entities such as a message or a channel. Unfortunately cost of maintaining a codebase with such mechanic in place was quite high in comparison to the amount of benefits it brought to our integrators. It also fell short when it came to ease of use. Integrators would have to forward custom-defined type definitions to multiple places seemingly unrelated to the code at hand.

### Main Reasons

There are two main issues with the generic mechanism.

#### Fragile

Integrators would have to define all of the custom types within the generic type even if they augmented only one or two specific entities which also meant that every time we issued a release with a new feature that extended the `DefaultGenerics`, just upgrading to the latest package version wouldn't suffice, the generic type would have to be manually adjusted as well even if not explicitly needed.

```ts title="types.ts"
export type LocalStreamChatGenerics = {
  // the only augmentations I (as an integrator) currently care about
  attachmentType: { myCustomThing?: string };
  userType: { profilePicture?: string };
  // the rest which would have to be defined to be accepted by the SDK
  channelType: Record<string, unknown>;
  commandType: string;
  eventType: Record<string, unknown>;
  memberType: Record<string, unknown>;
  messageType: Record<string, unknown>;
  pollOptionType: Record<string, unknown>;
  pollType: Record<string, unknown>;
  reactionType: Record<string, unknown>;
  // list would grow with each update to the `DefaultGenerics` type
};
```

#### Lack of Ease of Use

Integrators would have to import and apply generics to multitude of places to get proper type support.

```tsx title="custom-attachment.tsx"
import type { LocalStreamChatGenerics } from "./types.ts";

// [!code word:<LocalStreamChatGenerics>]
export const CustomAttachment = (
  props: AttachmentProps<LocalStreamChatGenerics>,
) => {
  const { client } = useChatContext<LocalStreamChatGenerics>();

  console.log(client.user?.profilePicture);

  const [attachment] = props.attachments;

  if (typeof attachment.myCustomThing === "string") {
    return <div>...</div>;
  }

  return <Attachment {...props} />;
};
```

```tsx title="root.tsx"
import { CustomAttachment } from "./custom-attachment.tsx";
import type { LocalStreamChatGenerics } from "./types.ts";

// [!code word:<LocalStreamChatGenerics>]
const Root = () => {
  const chatClient = useCreateChatClient<LocalStreamChatGenerics>({
    apiKey: "<api-key>",
    tokenOrProvider: "user.access.token",
    userData: { id: "<user-id>" },
  });

  return (
    <Chat client={chatClient}>
      <Channel Attachment={CustomAttachment}>...</Channel>
    </Chat>
  );
};
```

### Replacement - Module Augmentation

The transition to the new mechanism is fairly easy and pretty straightforward. Almost all of your custom declarations can stay as is, we'll just have to move them to a declaration file. In there we'll import `stream-chat` and define a specific interface to extend.

```ts title="stream-custom-data.d.ts"
import "stream-chat";

declare module "stream-chat" {
  // your "attachmentType" now becomes
  interface CustomAttachmentData {
    custom_property?: string;
  }

  // your "userType" now becomes
  interface CustomUserData {
    profile_picture?: string;
  }

  interface CustomCommandData {
    "custom-command": unknown;
    "other-custom-command": unknown;
  }
}
```

<admonition type="info">

**CustomCommandType**

Note that commands (`CustomCommandType`) are a special case which transforms from string union type (`commandType: 'custom-command' | 'other-custom-command'`) to an interface from which only the keys would be used, the value type is not important and `unknown` would suffice.

**ChannelResponse.name**

If you wish to continue using `ChannelResponse.name` property, you'll have to add it yourself by extending `CustomChannelData` interface since it's considered to be a custom field and is not part of the `Channel` entity by default.

</admonition>

Now as for the rest of your code; none of the members exported by the `stream-chat-react` (and `stream-chat`) no longer support `StreamChatGenerics` which means you'll have to remove them. Using our previous example your code should now look like this:

```tsx title="custom-attachment.tsx"
export const CustomAttachment = (props: AttachmentProps) => {
  // [!code highlight]
  const { client } = useChatContext(); // [!code highlight]

  console.log(client.user?.profile_picture);

  const [attachment] = props.attachments;

  if (typeof attachment.custom_property === "string") {
    return <div>...</div>;
  }

  return <Attachment {...props} />;
};
```

```tsx title="root.tsx"
import { CustomAttachment } from "./custom-attachment.tsx";

const Root = () => {
  const chatClient = useCreateChatClient({
    // [!code highlight]
    apiKey: "<api-key>",
    tokenOrProvider: "user.access.token",
    userData: {
      id: "<user-id>",
      profile_picture: "https://random.picture/1.jpg",
    },
  });

  return (
    <Chat client={chatClient}>
      <Channel Attachment={CustomAttachment}>...</Channel>
    </Chat>
  );
};
```

And that's it! One place, one definition, no need to pass anything else around your code. Everything is taken care of internally and all of your custom types are now accounted for.

<admonition type="danger">

If you don't care about specifying your custom types and prefer the previous behavior (casting) you can mark every unspecified key as `unknown`:

```ts title="stream-custom-data.d.ts"
import "stream-chat";

declare module "stream-chat" {
  interface CustomAttachmentData {
    [key: string]?: unknown;
  }
}
```

Now this is generally not recommended, but it is a possibility.

</admonition>

## Removal of the jsDelivr (UMD) Bundle

The package provided a bundle which was built using an outdated UMD format. If your intention was to use `stream-chat` imported directly from the CDN, see [esm.sh documentation](https://esm.sh/#docs) for a viable alternative.

```tsx
import { StreamChat } from "https://esm.sh/stream-chat";
```

## Replacement of FormatMessageResponse by LocalMessage type

The new type `LocalMessage` is used everywhere a local state message object representation is required. In general, if a message response object obtained from the server is to be stored in the state, then it has to be converted into `LocalMessage`. The new type contains key `error` to store the information about failed message creation server-side. Type `FormatMessageResponse` is an alias to `LocalMessage` and will be removed in the future.

With this change, the signature of `StreamChat.updateMessage()` method has been changed. The first argument - `message` - should be of type `LocalMessage | Partial<MessageResponse>` instead of `UpdatedMessage`.

---

This page was last updated at 2025-08-15T14:14:38.941Z.

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/ios-swift/upgrade-stream-chat-to-v9/](https://getstream.io/chat/docs/ios-swift/upgrade-stream-chat-to-v9/).