This guide explains the mental model behind advanced UI Kit customization. The deep
detail (file paths, symbol names) is grounded in the React v6 UI Kit; the same
concept applies to every platform, with the per-platform mechanism summarized in the
cross-platform table below.
The four pieces (src/utils/)
How the chain is built
AtCometChatUIKit.init(), each enabled extension’s .enable() is invoked, and each
extension’s enable() calls:
CometChatUIKit.getDataSource() returns ChatConfigurator.getDataSource() — the
head of that chain.
The call trace: “add a Share Location attachment option”
- The call hits the head decorator. It calls
super.getAttachmentOptions(...), which walks down the chain toMessagesDataSource, returning the base[image, video, audio, file]. - On the way back up, each decorator runs its own override —
const opts = super.getAttachmentOptions(...); opts.push(myExtensionAction); return opts;— so Polls pushes “Polls”, Doc pushes “Collaborative Document”, Whiteboard pushes “Collaborative Whiteboard”. - The caller receives the fully-accumulated default list, appends its own
CometChatMessageComposerAction, and hands the merged array to the composer.
getAllMessageTemplates() (each decorator pushes its
custom-message template), getMessageOptions() (each pushes its long-press
action), getAllTextFormatters(), etc.
Why this means “append, not replace”
Internally every decorator doessuper.getX().push(...). When you replace (pass
a list containing only your item), you discard the entire chain’s accumulated output
— which is why the default text/image/file bubbles, the camera/gallery/document
attachments, or reply/edit/delete options silently vanish. The rule “start from
getAllX() / the defaults, then push” literally mirrors the decorator chain. Pass
only your item to a templates= / attachmentOptions= prop that replaces, and
you have amputated the chain.
Cross-platform: same concept, different mechanism
How the components are stitched together
The DataSource chain answers “what options/templates are available.” It is one of four stitch layers that together turn separate components into a working chat surface.Layer 1 — Layout stitch (your app code)
The kit ships discrete components; you compose them. The canonical two-pane shape:- The Selector’s
onItemClickstoresselectedItemand passes it down asuserORgroup(mutually exclusive) to all three message components. - That shared
user/groupbinding is the whole “which conversation” wiring — Header, List, and Composer independently fetch/scope to the same target. - There is no composite that does this for you in the current web/RN/Angular/Android kits — composing these four is the integration.
Layer 2 — Component → DataSource stitch (defaults vs override)
Each component, when you don’t pass the prop, pulls its config from the DataSource chain itself:CometChatMessageList→ChatConfigurator.getDataSource().getAllMessageTemplates({...}).CometChatMessageComposer→getAttachmentOptions(...).
templates= / attachmentOptions= replaces that internal fetch with your
array — which is exactly why you must merge (start from getAllX() then push). This
is the bridge between Layer 1 (props) and the decorator chain.
Layer 3 — Render stitch (message → bubble via the template map)
CometChatMessageList turns each message into a bubble through a category_type
lookup map:
- It builds the map:
messagesTypesArray[el.category + "_" + el.type] = elfrom the templates (yours-merged-with-defaults). - For each message it resolves the slot views by key:
messagesTypesMap[item.getCategory() + "_" + item.getType()]?.contentView(item, …)— and similarlyheaderView/footerView/bottomView/statusInfoView, orbubbleViewto replace the whole bubble. getBubbleWrapperassembles those slot views into the rendered bubble.
type + category
match the message — that’s the entire contract. No matching map entry → nothing (or an
unknown-message fallback) renders. This is why the custom-message recipe is “register a
template with type: "location", category: "custom" + a contentView.”
Layer 4 — Runtime stitch (the event bus decouples the components)
The components do not call each other directly. They communicate through pub/sub event buses —CometChatMessageEvents, CometChatUIEvents, CometChatGroupEvents:
CometChatUIKit.sendCustomMessage(msg)emitsCometChatMessageEvents.ccMessageSent; the List is subscribed and appends the message optimistically → the bubble appears instantly (setsenderon the message before sending so the optimistic bubble has one).- Incoming real-time messages arrive via the SDK message listener the List registers → appended the same way.
- Group mutations (
ccOwnershipChanged,ccGroupMemberAdded, …) flow the same pub/sub route, so the Header / Members views update without the List knowing about them.
The event-bus catalog (src/events/)
Six buses, two naming conventions. cc* = UI-Kit-emitted (a kit component reporting a
local user action — subscribe to react to what the user did, often optimistically).
on* = SDK pass-through (real-time inbound, mirroring the Chat SDK listeners —
subscribe instead of registering a raw SDK listener).
How to use them: subscribe with
Bus.event.subscribe(cb) and always unsubscribe()
on unmount (every kit component does). For your own code reacting to chat state, prefer
these over raw SDK listeners — they’re already the kit’s source of truth and fire for both
UI-originated and SDK-originated changes. Emit cc* yourself (e.g. ccMessageSent) only
when you send a message outside CometChatUIKit.sendX and want the kit’s list to update —
but CometChatUIKit.sendCustomMessage already emits it, which is why a custom bubble appears
without any manual event work.
Putting it together — the full flow for “send a custom location message”:
Composer attachment onClick → sendLocationMessage builds a CustomMessage →
CometChatUIKit.sendCustomMessage → emits ccMessageSent → MessageList (subscribed)
appends it → the render stitch looks up custom_location in the template map → calls your
template’s contentView → your location bubble renders. On reload, the same bubble comes
from history only if the messagesRequestBuilder includes the custom category +
location type (Layer 2).
The mental model in one line
Source references
cometchat-uikit-react:src/utils/{ChatConfigurator,DataSource,DataSourceDecorator,MessagesDataSource},src/components/Extensions/*/*ExtensionDecorator,src/CometChatUIKit/CometChatUIKit.ts.- The official React sample app’s live-location feature (attachment option + custom message + custom bubble) is a complete working reference for all four layers.