# Typescript

`stream-chat-react-native` and `stream-chat-js` are written in TypeScript and provide full type support.

The SDK supports custom fields on messages, channels, users, and more. The TypeScript implementation provides static type safety for both built-in and custom data.

## Best Practices

- Declare custom data types in a single `.d.ts` file to avoid conflicts.
- Extend `Default*Data` types to stay compatible with future SDK changes.
- Avoid using index signatures unless you truly need untyped custom fields.
- Keep custom types narrow and explicit to improve autocomplete and safety.
- Re-run type checks after SDK upgrades to catch breaking changes early.

## Module augmentation and interface merging

Use [module augmentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation) to define custom data shapes.

See the full list of customizable interfaces [here](https://github.com/GetStream/stream-chat-js/blob/master/src/custom_types.ts), which are [merged](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#merging-interfaces) with your definitions.

<admonition type="note">

The interfaces concerning the entities which can be extended will always be named in the format of `Custom<uppercase-entity-name>Data`.

</admonition>

Define these in a module declaration file (`.d.ts`), for example:

```ts
import { DefaultChannelData } from "stream-chat-react-native";

declare module "stream-chat" {
  interface CustomChannelData extends DefaultChannelData {
    "custom-channel-property": string;
  }

  interface CustomAttachmentData {
    id: string;
    size: number;
  }

  interface CustomCommandData {
    poke: unknown;
  }
}
```

<admonition type="info">

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.

</admonition>

The declaration adds `custom-channel-property` to `Channel` and keeps default properties. It also adds `id` and `size` to `Attachment` (without extending the default SDK attachment type). Command types add a new `poke` command.

<admonition type="note">

You can place the `.d.ts` file anywhere; the TypeScript compiler will consume it.

Note: declaration merging does **not** work as an extension chain; it overrides unless you explicitly extend.

</admonition>

## Default SDK properties

The React Native SDK defines additional properties on top of `stream-chat` and exposes them in default types you can reuse.

You may find a full list of these defaults [here](https://github.com/GetStream/stream-chat-react-native/v8/blob/V8-release/package/src/types/stream-chat-common-custom-data.d.ts), as well as the module declaration used in the SDK itself. If interfaces are defined within your own application, they will **override** the interfaces in the SDK rather than extend them (and this is done by design).

<admonition type="note">

The default SDK types are always going to be named in the format of `Default<uppercase-entity-name>Data`.

</admonition>

When defining custom data, you can use these defaults or omit them. In the example above, `CustomChannelData` respects SDK defaults while `CustomAttachmentData` does not.

If you do not wish to extend any of the default types but still require strong typing based on the SDK's types, you may do the following:

```typescript
import {
  DefaultAttachmentData,
  DefaultChannelData,
  DefaultCommandData,
  DefaultEventData,
  DefaultMemberData,
  DefaultMessageData,
  DefaultPollData,
  DefaultPollOptionData,
  DefaultReactionData,
  DefaultThreadData,
  DefaultUserData,
} from "stream-chat-react-native";

declare module "stream-chat" {
  /* eslint-disable @typescript-eslint/no-empty-object-type */

  interface CustomAttachmentData extends DefaultAttachmentData {}

  interface CustomChannelData extends DefaultChannelData {}

  interface CustomCommandData extends DefaultCommandData {}

  interface CustomEventData extends DefaultEventData {}

  interface CustomMemberData extends DefaultMemberData {}

  interface CustomUserData extends DefaultUserData {}

  interface CustomMessageData extends DefaultMessageData {}

  interface CustomPollOptionData extends DefaultPollOptionData {}

  interface CustomPollData extends DefaultPollData {}

  interface CustomReactionData extends DefaultReactionData {}

  interface CustomThreadData extends DefaultThreadData {}

  /* eslint-enable @typescript-eslint/no-empty-object-type */
}
```

<admonition type="note">

Even if many defaults are currently empty, extending them now protects you from future changes.

In that regard, it is important to add all of them here unless you have a very good reason not to.

</admonition>

Here is a more realistic example:

```typescript
import {
  DefaultAttachmentData,
  DefaultChannelData,
} from "stream-chat-react-native";

declare module "stream-chat" {
  interface CustomChannelData extends DefaultChannelData {
    color: string;
    topic: "gardening" | "cats" | "f1";
  }

  interface CustomUserData {
    nickname: string;
  }

  interface CustomMessageData {
    isSecret: boolean;
  }

  interface CustomAttachmentData extends DefaultAttachmentData {
    lat?: string;
    lon?: string;
  }

  interface CustomReactionData {
    // Turn off type checks for custom properties
    [key: string]: unknown;
  }
}
```

In this case, your custom data will be properly typed:

```typescript
let channel: Channel | undefined;
console.log(channel?.data?.topic);

let user: User | undefined;
console.log(user?.nickname);

let message: StreamMessage | undefined;
console.log(message?.isSecret);

let attachment: Attachment | undefined;
console.log(attachment?.lat);
console.log(attachment?.lon);

let reaction: Reaction | undefined;
// CustomReactionData allows any key on Reaction
console.log(reaction?.foo);
```

Any types you don't define fall back to the `stream-chat` defaults.

## Disabling type checks

If you do not want type checks for custom properties on a specific entity, you can define the custom interface like this:

```ts
interface CustomMessageData {
  // Turn off type checks for custom properties
  [key: string]: unknown;
}
```

This allows any custom keys.


---

This page was last updated at 2026-04-17T17:33:45.596Z.

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react-native/v8/customization/typescript/](https://getstream.io/chat/docs/sdk/react-native/v8/customization/typescript/).