Skip to main content
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. 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. 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. The four template and type methods must call super and extend the result. Returning your own list wholesale drops every built-in type.
getId is the exception — it must not call super.The base implementation on DataSourceDecorator throws, it does not delegate:
Both ways of getting it wrong fail quietly or confusingly:Return a string unique to your decorator.

When to register

Register after CometChatUIKit.login() resolves — not after init().login() calls enableExtensions(), whose first statement re-initialises the data source:
ChatConfigurator.init() replaces dataSource and resets the names dedup list:
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.

Example: a poll message type

Register it after login:

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():
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().
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

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.