> ## Documentation Index
> Fetch the complete documentation index at: https://www.cometchat.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom Message Types

> Register a custom message type in the CometChat React Native UI Kit using DataSourceDecorator, ChatConfigurator and ExtensionsDataSource — the five required overrides, and the registration timing that makes them stick.

> [accordion] AI Integration Quick Reference
>
> | Field | Value |
> | - | - |
> | Package | `@cometchat/chat-uikit-react-native` |
> | Import | `import { ChatConfigurator, DataSourceDecorator, ExtensionsDataSource } from "@cometchat/chat-uikit-react-native";` |
> | Key classes | `DataSourceDecorator` · `ChatConfigurator` · `ExtensionsDataSource` · `MessageDataSource` |
> | Required overrides | **Five** — `getAllMessageTemplates` · `getMessageTemplate` · `getAllMessageTypes` · `getAllMessageCategories` · `getId` |
> | Register with | `ChatConfigurator.enable(source => new MyDataSource(source))` |
> | Prerequisites | `CometChatUIKit.init()` completed **and** the session established — via `CometChatUIKit.login()` **or** a restored session from `CometChatUIKit.getLoggedInUser()` — [details](#when-to-register) |
> | Constraints | `getId` must not call `super` and must be unique · registration before the session is established (`login()` **or** a restored `getLoggedInUser()` session) is discarded · an `ExtensionsDataSource` must override `enable()` |
> | Primary output | The custom type renders in `CometChatMessageList` **and** survives a reload |
> | Related | [Message List](https://www.cometchat.com/docs/ui-kit/react-native/message-list.md) · [Message Composer](https://www.cometchat.com/docs/ui-kit/react-native/message-composer.md) · [Methods](https://www.cometchat.com/docs/ui-kit/react-native/methods.md) |

The UI Kit has no plugin API. It customises through a **decorator chain**: you wrap the active data source in your own `DataSourceDecorator`, override the methods you need, and register the result with `ChatConfigurator`.

```mermaid theme={null}
graph LR
    CC["ChatConfigurator.dataSource"] --> D["Your DataSourceDecorator"]
    D --> M["MessageDataSource (default)"]
```

`MessageDataSource` is the default implementation `ChatConfigurator` starts from. Every decorator you register wraps whatever is currently active, so calling `enable()` more than once layers decorators rather than replacing them.

***

## The five required overrides

A custom message type has to be declared twice — once so the list knows how to **render** it, and once so the list knows to **fetch** it — plus an identity so `ChatConfigurator` can tell your decorator apart from the others.

| Half | Methods | Why |
| - | - | - |
| Render | `getAllMessageTemplates` · `getMessageTemplate` | Maps the type to a bubble |
| **Fetch** | **`getAllMessageTypes`** · **`getAllMessageCategories`** | `CometChatMessageList` builds `setTypes`/`setCategories` from these |
| **Identity** | **`getId`** | `ChatConfigurator.enable()` dedupes on it |

Miss the fetch half and the bubble renders optimistically when the message is sent, then disappears on reload — the type is filtered out **server-side**, with no error in the console, on the listener, or in the network response. See [Filtering Messages](https://www.cometchat.com/docs/ui-kit/react-native/message-list.md#filtering-messages).

The four template and type methods must call `super` and extend the result. Returning your own list wholesale drops every built-in type.

> [!WARNING]
>
> **`getId` is the exception — it must not call `super`.**
>
> The base implementation on `DataSourceDecorator` throws, it does not delegate:
>
> ```ts theme={null}
> getId(): string {
>   throw new Error("Method not implemented.");
> }
> ```
>
> Both ways of getting it wrong fail quietly or confusingly:
>
> | Mistake | What happens |
> | - | - |
> | No `getId` override, or `return super.getId()` | Crashes at `ChatConfigurator.enable()` with `Method not implemented.` — a message naming neither `getId` nor your decorator. The class is **not** `abstract`, so TypeScript does not catch it. |
> | `return this.dataSource.getId()` (the wrapped source's id) | `enable()` finds the id already in its `names` list, decides the decorator is registered, and **skips it silently**. Nothing renders and nothing errors. |
>
> Return a string unique to your decorator.

***

## When to register

> [!WARNING]
>
> **Register after the session is established — not after `init()`.** For a fresh sign-in that is when `CometChatUIKit.login()` resolves; for an app that restores an existing session on relaunch it is when `CometChatUIKit.getLoggedInUser()` resolves (that path never calls `login()`).
>
> `login()` calls `enableExtensions()`, whose first statement re-initialises the data source:
>
> ```ts theme={null}
> private static enableExtensions() {
>   ChatConfigurator.init(); // re-initialize data source
> ```
>
> `ChatConfigurator.init()` replaces `dataSource` and resets the `names` dedup list:
>
> ```ts theme={null}
> static init(initialSource?: DataSource) {
>   this.dataSource = initialSource ?? new MessageDataSource();
>   this.names = ["message_utils"];
>   this.names.push(this.dataSource.getId());
> }
> ```
>
> `enableExtensions()` runs from every entry point that establishes or changes the session — `init()`, `login()` and `getLoggedInUser()` among them. Anything registered at module scope, or straight after `init()`, is gone by the time the user is logged in.

```tsx lines theme={null}
import { CometChatUIKit, ChatConfigurator } from "@cometchat/chat-uikit-react-native";

await CometChatUIKit.init(uiKitSettings);
await CometChatUIKit.login({ uid: "cometchat-uid-1" });

// Only now does the registration survive.
ChatConfigurator.enable((source) => new PollDataSource(source));
```

If your app relaunches into a restored session via `CometChatUIKit.getLoggedInUser()` instead of calling `login()`, run the same registration after that call resolves. Alternatively, register through `uiKitSettings.extensions` so it is applied on every session entry point rather than tied to one call.

***

## Example: a poll message type

```tsx lines theme={null}
import { CometChat } from "@cometchat/chat-sdk-react-native";
import {
  ChatConfigurator,
  CometChatMessageTemplate,
  CometChatTheme,
  DataSource,
  DataSourceDecorator,
} from "@cometchat/chat-uikit-react-native";
import { Text } from "react-native";

const POLL_TYPE = "poll";

class PollDataSource extends DataSourceDecorator {
  // Required. Must be unique and must not delegate to the wrapped source.
  getId(): string {
    return "poll_data_source";
  }

  // Fetch half — without these the bubble vanishes on reload.
  getAllMessageTypes(): string[] {
    return [...super.getAllMessageTypes(), POLL_TYPE];
  }

  getAllMessageCategories(): string[] {
    return [...super.getAllMessageCategories(), CometChat.CATEGORY_CUSTOM];
  }

  // Render half.
  getAllMessageTemplates(
    theme: CometChatTheme,
    additionalParams?: any
  ): CometChatMessageTemplate[] {
    return [
      ...super.getAllMessageTemplates(theme, additionalParams),
      this.getPollTemplate(),
    ];
  }

  getMessageTemplate(
    messageType: string,
    messageCategory: string,
    theme: CometChatTheme,
    additionalParams?: any,
    message?: CometChat.BaseMessage
  ): CometChatMessageTemplate | null {
    if (
      messageType === POLL_TYPE &&
      messageCategory === CometChat.CATEGORY_CUSTOM
    ) {
      return this.getPollTemplate();
    }
    return super.getMessageTemplate(
      messageType,
      messageCategory,
      theme,
      additionalParams,
      message
    );
  }

  private getPollTemplate(): CometChatMessageTemplate {
    return new CometChatMessageTemplate({
      type: POLL_TYPE,
      category: CometChat.CATEGORY_CUSTOM,
      ContentView: (message: CometChat.BaseMessage) => {
        const data = (message as CometChat.CustomMessage).getCustomData() as {
          question?: string;
        };
        return <Text>{data?.question ?? "Poll"}</Text>;
      },
    });
  }
}

export function registerPollType(source: DataSource) {
  return new PollDataSource(source);
}
```

Register it after login:

```tsx lines theme={null}
ChatConfigurator.enable(registerPollType);
```

***

## Registering through `uiKitSettings.extensions`

`enableExtensions()` re-applies `uiKitSettings.extensions` *after* the reset, so an `ExtensionsDataSource` passed in settings is the one registration route that survives `login()`. It has its own trap.

`ExtensionsDataSource` is abstract. It requires `addExtension()` and `getExtensionId()`, and ships a concrete `enable()`:

```ts theme={null}
abstract class ExtensionsDataSource {
  abstract addExtension(): void;
  abstract getExtensionId(): string;

  enable(): void {
    CometChat.isExtensionEnabled(this.getExtensionId()).then((enabled) => {
      if (enabled) this.addExtension();
    });
  }
}
```

> [!WARNING]
>
> **A custom message type is not a dashboard extension.**
>
> `isExtensionEnabled("poll")` resolves `false` for any type you invented, so the inherited `enable()` never calls `addExtension()` and your decorator is never registered — silently. Override `enable()` as well, and the dashboard check is bypassed.
>
> Both steps or neither: passing the settings without overriding `enable()` fails exactly as quietly as registering before `login()`.

```tsx lines theme={null}
import {
  ChatConfigurator,
  CometChatUIKit,
  ExtensionsDataSource,
} from "@cometchat/chat-uikit-react-native";
// PollDataSource is the decorator from the example above.

class PollExtension extends ExtensionsDataSource {
  getExtensionId(): string {
    return "poll";
  }

  addExtension(): void {
    ChatConfigurator.enable((source) => new PollDataSource(source));
  }

  // Required: the inherited enable() gates on a dashboard check that is
  // false for a custom type, so addExtension() would never run.
  override enable(): void {
    this.addExtension();
  }
}

CometChatUIKit.init({
  appId: "APP_ID",
  region: "REGION",
  authKey: "AUTH_KEY",
  // Spread the defaults: `extensions` replaces them, it does not add to them.
  extensions: [...CometChatUIKit.defaultExtensions, new PollExtension()],
});
```

> [!WARNING]
>
> `extensions` **replaces** the built-in list. Passing only `[new PollExtension()]` turns off stickers, link preview, translation, polls, collaborative document and whiteboard, and thumbnail generation. Always spread `CometChatUIKit.defaultExtensions` first.

Passed this way, the type is re-registered automatically on every `login()`, so you do not have to sequence the call yourself.

***

## Choosing a route

| Route | Survives `login()` | Extra requirement |
| - | - | - |
| `ChatConfigurator.enable()` after `login()` **or** `getLoggedInUser()` resolves | Yes, until the next `login()` / `getLoggedInUser()` | Sequence the call yourself |
| `ExtensionsDataSource` in `uiKitSettings.extensions` | Yes, re-applied on every session change | Must override `enable()` |

Use the settings route for a type your app always needs. Use the direct call for a type you register conditionally after the user is known.

***

## Related

* [Message List → Filtering Messages](https://www.cometchat.com/docs/ui-kit/react-native/message-list.md#filtering-messages) — why the fetch half matters.
* [Message Composer](https://www.cometchat.com/docs/ui-kit/react-native/message-composer.md) — sending a custom message.
* [Methods](https://www.cometchat.com/docs/ui-kit/react-native/methods.md) — `CometChatUIKit.init` and `login`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.