TypeScript & Custom Data Types

Both stream-chat-react and stream-chat have full TypeScript support. They provide type safety, but also allow integrator-defined typings to be taken into consideration when working with Stream entities such as message or channel through the use of module augmentation (declaration merging).

stream-chat provides a set of extendable interfaces which merge with the base types of Stream entities:

  1. CustomAttachmentData
  2. CustomChannelData
  3. CustomCommandData
  4. CustomEventData
  5. CustomEventTypes
  6. CustomMemberData
  7. CustomMessageData
  8. CustomPollOptionData
  9. CustomPollData
  10. CustomReactionData
  11. CustomUserData
  12. CustomThreadData
  13. CustomMessageComposerData

Best Practices

  • Use module augmentation to keep custom data types centralized.
  • Extend DefaultChannelData if you rely on the default channel display helpers or channel-avatar related UI.
  • Keep custom types minimal to avoid bloated payloads.
  • Regenerate or re-check types after SDK upgrades.
  • Use unknown for command payloads when only keys matter.

Note that CustomChannelData is extended within stream-chat-react with commonly used fields (subtitle, image, name) in DefaultChannelData. Extending your declarations with these defaults (see below) isn’t required, but some default helpers and avatar-related channel UI still expect image and name to have the usual shapes. The default ChannelHeader no longer renders subtitle, so that field is only relevant if your own app code or custom components use it.

Declare Custom Data Types

To extend these custom interfaces you'll have to create a declaration file within your codebase and make sure it's loaded by the TypeScript. Within this file we'll import stream-chat and specific defaults from stream-chat-react and then define the interface augmentations:

stream-custom-data.d.ts
import "stream-chat";
import type { DefaultChannelData } from "stream-chat-react";

declare module "stream-chat" {
  interface CustomChannelData extends DefaultChannelData {}

  interface CustomMessageData {
    custom_property?: string;
  }

  interface CustomUserData {
    profile_picture?: string;
  }

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

  interface CustomEventTypes {
    "custom.event": unknown;
  }
}

Note that CustomCommandData and CustomEventTypes are special cases from which only the keys are used, the value type is not important and unknown would suffice. CustomCommandData keys become valid custom command names, while CustomEventTypes keys are added to the EventTypes union — letting you use your own event type strings wherever an EventTypes is expected (for example, the type passed to channel.sendEvent).

That's it, you can check if your augmentations work with similar code:

import { StreamChat } from "stream-chat";

const client = new StreamChat("any-key");

const response = await client.getMessage("id");

const customProperty = response.message.custom_property; // should be `string | undefined` as per our declaration