agentsclimarketplace

Cometchat native customization

Skill cometchat/cometchat-skills/packages/skills-native/skills/cometchat-native-customization

Customize the CometChat React Native UI Kit without forking — four-tier model: props → request builders → text formatters + message templates → DataSource decorators + event bus.From its SKILL.md

Install
npx -y skills add cometchat/cometchat-skills --skill cometchat-native-customization

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.

What its file declares

Copied from the file, not written here

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

21.1 KB, ~5.2k tokens by cl100k_base, as published. Nobody here has run it

Purpose

Teaches Claude how to change the behavior or appearance of the React Native UI Kit without modifying the kit itself. Four tiers, from cheapest to deepest:

Tier 1 — Props            (95% of asks solved here)
Tier 2 — RequestBuilder   (filter what data loads)
Tier 3 — Formatters + Templates   (change how text / messages render)
Tier 4 — DataSource decorators + Events  (last resort, powerful)

Always try Tier 1 first. Escalate only when the tier can't do what the user wants.

Read cometchat-native-components first — the catalog is the source of truth for prop names, slot views, and event listener names that this skill builds on.

Ground truth: docs/ui-kit/react-native/custom-text-formatter-guide.mdx, mentions-formatter-guide.mdx, shortcut-formatter-guide.mdx, url-formatter-guide.mdx, events.mdx, methods.mdx, property-changes.mdx, and the kit's source at packages/ChatUiKit/src/shared/formatters/ and packages/ChatUiKit/src/shared/events/.


Four-tier triage — pick the right tier before writing any code

When a user says "I want X" for a CometChat component:

If they want to...Use TierCost
Hide a feature (thread option, receipts, edit, etc.)Tier 1 — hide* / *Visibility props1 line of JSX
Customize a subsection (header title, subtitle, avatar, empty state)Tier 1 — <Slot>View prop1 component
Filter what loads (only show online users, exclude blocked, include tags)Tier 2 — *RequestBuilder1 builder
Change how URLs / mentions / hashtags / emojis render inlineTier 3 — textFormattersSubclass of CometChatTextFormatter
Render a custom message type (custom bubble, custom interactive msg)Tier 3 — templates + CometChatMessageTemplate1 template + 1 renderer
React to events from another component ("they deleted a message, now reload my view")Tier 4 — CometChatUIEventHandlerListener
Rewrite how data flows through the kit (custom conversation sorting, override user-fetch logic)Tier 4 — DataSourceDecoratorClass extension

If a user's ask fits Tier 1 but you jumped to Tier 3, you've written 50 lines that a 1-line prop could have replaced. Start low.


Tier 1 — Props (hide / slot views / styles)

cometchat-native-components is the full catalog. Three prop families cover most customization:

1a. hide* / *Visibility flags

Turn features off with a single prop:

<CometChatMessageList
  user={selectedUser}
  hideReplyInThreadOption     // already mandatory — see components § 11
  hideReceipts
  hideReactions={false}
  hideTranslateMessageOption
  hideMessagePrivatelyOption
  hideReplyOption={false}
/>

Full list of hide* props per component: cometchat-native-components. Check there before writing custom code.

1b. <Slot>View props — replace a section

Every component has PascalCase slot props for replacing named sections of its default UI:

<CometChatMessageHeader
  user={selectedUser}
  TitleView={(user, group) => <Text style={styles.customTitle}>{user?.getName()}</Text>}
  SubtitleView={(user, group) => <OnlineStatus user={user} />}
  LeadingView={(user, group) => <CustomAvatar user={user} />}
  TrailingView={(user, group) => <CustomActions user={user} />}
  AuxiliaryButtonView={(user, group) => <CometChatCallButtons user={user} group={group} />}
/>

Slot functions receive the same data the default view would have (typically user, group, or a single entity). They return RN JSX.

For custom views that should match the theme, use useTheme():

import { useTheme } from "@cometchat/chat-uikit-react-native";

function CustomTitle({ user }: any) {
  const theme = useTheme();
  return (
    <Text style={{
      color: theme.color.textPrimary,
      fontFamily: theme.typography.heading3.fontFamily,
      fontSize: theme.typography.heading3.fontSize,
    }}>
      {user?.getName()}
    </Text>
  );
}

See cometchat-native-theming § 8 for more on useTheme().

1c. style={{ ... }} prop — nested styling

Each component accepts a nested-object style prop (see cometchat-native-components § 13):

<CometChatConversations
  style={{
    containerStyle: { backgroundColor: "#FAFAFA" },
    itemStyle: {
      avatarStyle: { containerStyle: { borderRadius: 8 } },
    },
  }}
/>

Prefer theme-level changes (via cometchat-native-theming) for app-wide color shifts; use style={{}} only for one-off overrides on a single component instance.


Tier 2 — RequestBuilder filtering

For "I want to show a subset of X", use the matching *RequestBuilder. Never post-filter in-render.

import { CometChat } from "@cometchat/chat-sdk-react-native";

// Only conversations in a specific tag group
<CometChatConversations
  conversationsRequestBuilder={
    new CometChat.ConversationsRequestBuilder()
      .setLimit(20)
      .setUserTags(["premium"])
      .setConversationType(CometChat.RECEIVER_TYPE.USER)
  }
/>

// Only online users, exclude blocked
<CometChatUsers
  usersRequestBuilder={
    new CometChat.UsersRequestBuilder()
      .setLimit(30)
      .setStatus("online")
      .setSearchKeyword("")
      .friendsOnly(false)
  }
/>

// Only groups you've joined
<CometChatGroups
  groupsRequestBuilder={
    new CometChat.GroupsRequestBuilder()
      .setLimit(30)
      .joinedOnly(true)
  }
/>

// Message list — exclude system messages
<CometChatMessageList
  user={user}
  messageRequestBuilder={
    new CometChat.MessagesRequestBuilder()
      .setUID(user.getUid())
      .setLimit(30)
      .setCategories(["message"])   // exclude "call", "action"
      .hideReplies(false)
  }
  hideReplyInThreadOption
/>

Each request builder is chainable. The @cometchat/chat-sdk-react-native exports the builder classes — import them from the SDK, not the UI Kit.

Finding the right method

Request builder methods are documented at cometchat.com/docs/sdk/react-native (or query the docs MCP). Common ones:

BuilderUseful methods
ConversationsRequestBuilder.setLimit(n), .setUserTags([...]), .setGroupTags([...]), .setConversationType(type), .withTags(true), .withUserAndGroupTags(true)
UsersRequestBuilder.setLimit(n), .setStatus("online"), .setSearchKeyword(str), .friendsOnly(bool), .setTags([...]), .setUIDs([...]), .hideBlockedUsers(bool)
GroupsRequestBuilder.setLimit(n), .setSearchKeyword(str), .joinedOnly(bool), .setTags([...]), .setGroupTypes([...])
MessagesRequestBuilder.setUID(uid) / .setGUID(guid), .setLimit(n), .setCategories([...]), .setTypes([...]), .hideReplies(bool), .setTags([...]), .setParentMessageId(id)
GroupMembersRequestBuilder.setLimit(n), .setSearchKeyword(str), .setScopes([...])

Tier 3 — Text formatters + message templates

For "change how text or messages render", Tier 3 is the right level. Two sub-patterns:

3a. Custom text formatter — inline text patterns

CometChatTextFormatter is an abstract base class for matching inline text patterns (hashtags, keywords, emoji shortcodes, custom tags) and replacing them with custom JSX.

import {
  CometChatTextFormatter,
  SuggestionItem,
} from "@cometchat/chat-uikit-react-native";
import { CometChat } from "@cometchat/chat-sdk-react-native";
import { Text, View, StyleSheet } from "react-native";

class HashtagFormatter extends CometChatTextFormatter {
  constructor() {
    super();
    this.setTrackingCharacter("#");              // optional — triggers suggestion list
    this.setRegexPatterns([/\B#(\w+)\b/g]);       // all matches get formatted
  }

  // Called for each bubble's text; return string | JSX
  getFormattedText(
    inputText: string | null | React.ReactNode,
  ): string | React.ReactNode {
    if (typeof inputText !== "string") return inputText;
    const parts = inputText.split(/(\B#\w+\b)/g);
    return (
      <Text>
        {parts.map((part, i) =>
          part.match(/^#\w+$/)
            ? <Text key={i} style={styles.hashtag} onPress={() => openHashtag(part)}>{part}</Text>
            : <Text key={i}>{part}</Text>,
        )}
      </Text>
    );
  }

  // Optional — called before a message is sent. Transform the outgoing message.
  handlePreMessageSend(message: CometChat.TextMessage): CometChat.TextMessage {
    // e.g. attach the list of hashtags to the message metadata
    return message;
  }

  // Optional — for suggestion-list support (triggered by `#`)
  search(searchKey: string): void {
    // Fetch matching hashtags from your backend, then:
    // this.setSearchData([{ id: "tag1", title: "#typescript" }]);
  }
}

const styles = StyleSheet.create({
  hashtag: { color: "#2563EB", fontWeight: "600" },
});

Register the formatter by passing it to both CometChatMessageList and CometChatMessageComposer:

const formatters = [
  new CometChatMentionsFormatter(),   // keep the built-in ones
  new CometChatUrlsFormatter(),
  new HashtagFormatter(),             // add yours
];

<CometChatMessageList
  user={selectedUser}
  textFormatters={formatters}
  hideReplyInThreadOption
/>
<CometChatMessageComposer
  user={selectedUser}
  textFormatters={formatters}
/>

3b. Custom message template — entire custom bubble

For rendering a totally custom message type (interactive cards, scheduling, forms), use CometChatMessageTemplate.

import {
  CometChatMessageTemplate,
  CometChatUiKitConstants,
} from "@cometchat/chat-uikit-react-native";

const pollTemplate = new CometChatMessageTemplate({
  type: "poll",
  category: CometChatUiKitConstants.MessageCategoryConstants.custom,
  ContentView: (message, alignment) => (
    <PollBubble message={message} alignment={alignment} />
  ),
  BottomView: (message, alignment) => (
    <PollVoteCounts message={message} />
  ),
  options: (loggedInUser, message, group) => [
    /* CometChatMessageOption[] — custom long-press menu items */
  ],
});

<CometChatMessageList
  user={selectedUser}
  templates={[pollTemplate, ...defaultTemplates]}   // merge with defaults
  hideReplyInThreadOption
/>

Getting the default templates to merge with:

import { ChatConfigurator } from "@cometchat/chat-uikit-react-native";
const defaults = ChatConfigurator.getDataSource().getAllMessageTemplates();
<CometChatMessageList templates={[pollTemplate, ...defaults]} />

When to use text formatter vs message template

Use formatter (Tier 3a)Use template (Tier 3b)
Change how TEXT inside a bubble renders (hashtags, URLs, mentions, emoji shortcodes)Render a completely different bubble body
Content is still a TextMessageContent is a custom message type (sent via CometChat.sendCustomMessage)
Doesn't need its own long-press optionsNeeds custom message options (vote, claim, accept, etc.)

Tier 4 — DataSource decorators + event bus

When Tiers 1-3 can't do it, you're modifying how data flows through the UI Kit. Two mechanisms:

4a. Event bus — CometChatUIEventHandler

Subscribe to events that UI Kit components emit so your own code can react.

import { CometChatUIEventHandler } from "@cometchat/chat-uikit-react-native";
import { useEffect } from "react";

function AppScreen() {
  useEffect(() => {
    const listenerId = "APP_MESSAGE_LISTENER";

    CometChatUIEventHandler.addMessageListener(listenerId, {
      ccMessageSent: ({ message, status }) => {
        // status === "inProgress" | "sent"
        analytics.track("message_sent", { id: message.getId() });
      },
      ccMessageEdited: ({ message }) => { /* ... */ },
      ccMessageDeleted: ({ message }) => { /* ... */ },
      ccMessageRead: ({ message }) => { /* ... */ },
      ccLiveReaction: ({ reaction }) => { /* ... */ },
    });

    return () => CometChatUIEventHandler.removeMessageListener(listenerId);
  }, []);

  return /* ... */;
}

Event listener API reference

ListenerUse when...
addMessageListenerreacting to any message-related event (sent, edited, deleted, read, reactions)
addConversationListenerreacting to conversation-level events (ccConversationDeleted, ccUpdateConversation)
addUserListenerreacting to user actions (ccUserBlocked, ccUserUnblocked)
addGroupListenerreacting to group lifecycle (ccGroupCreated, ccGroupDeleted, ccGroupLeft, ccGroupMemberScopeChanged, ccGroupMemberKicked, ccGroupMemberBanned, ccGroupMemberJoined, ccGroupMemberAdded, ccOwnershipChanged, etc.)
addCallListenerreacting to call events (onIncomingCallAccepted, onCallEnded, onCallInitiated, etc.)

Every pair has a matching remove*Listener(id) — always call it in the cleanup of your useEffect to avoid duplicate listeners on re-render.

Listener ID uniqueness matters. Use a constant per component/feature. Colliding IDs cause only the latest-registered listener to fire.

4b. DataSource decorators

DataSourceDecorator and MessageDataSource wrap the kit's internal data source to override specific methods without forking the whole kit.

When to reach for this: overriding how user data is fetched, how conversations are sorted, adding custom message metadata to every sent message, intercepting attachment uploads.

Minimum pattern:

import {
  DataSource,
  DataSourceDecorator,
  ChatConfigurator,
} from "@cometchat/chat-uikit-react-native";

class MyDataSource extends DataSourceDecorator {
  constructor(source: DataSource) {
    super(source);
  }

  // Override only the method you want to change
  getConversationsRequestBuilder() {
    const builder = super.getConversationsRequestBuilder();
    builder.setUserAndGroupTags(true);
    return builder;
  }

  getMessageTemplate() {
    const defaults = super.getMessageTemplate();
    return [myCustomTemplate, ...defaults];
  }
}

// Register the decorator before init — wraps the default data source
ChatConfigurator.dataSource = new MyDataSource(ChatConfigurator.getDataSource());
await CometChatUIKit.init(settings);

This is an escape hatch, not a first tool. If you find yourself reaching for Tier 4, re-check whether Tier 1 (props) or Tier 3 (templates) could have solved it. Templates + slot views cover most "custom behavior" asks.

4c. Extensions datasource (for extension-like deep behavior)

ExtensionsDataSource is the base class for registering an extension-shaped chunk of behavior (its own composer action + its own bubble + its own data handling) — this is what PollsExtension, StickersExtension, etc. extend internally. You'd only subclass this if you're shipping a reusable feature module across apps.

For a single app, use DataSourceDecorator instead.


5. Recipes (common customization asks → right tier)

"Filter the conversation list to just premium users"

Tier 2 — conversationsRequestBuilder with .setUserTags(["premium"]).

"Custom empty state for the users list"

Tier 1 — EmptyStateView slot prop on CometChatUsers.

"Custom message bubble for incoming messages only"

Tier 3b — CometChatMessageTemplate with a ContentView that branches on alignment === "receive". Or simpler — Tier 1 messageListStyles.receiveBubbleStyle in the theme (see cometchat-native-theming § 6).

"Show a custom view when the user types @"

Tier 3a — subclass CometChatMentionsFormatter (or extend CometChatTextFormatter), implement search(key) + setSearchData([...]) with your own suggestion source.

"When a message is sent, log it to our analytics"

Tier 4a — CometChatUIEventHandler.addMessageListener with ccMessageSent handler.

"When a group is deleted, remove it from my local cache + navigate away"

Tier 4a — addGroupListener with ccGroupDeleted handler.

"Render custom avatars for all users based on their department"

Tier 1 — LeadingView slot on CometChatConversations + CometChatUsers + CometChatMessageHeader.

"Disable the file attachment option"

Tier 1 — filter the attachmentOptions prop on CometChatMessageComposer:

<CometChatMessageComposer
  user={user}
  attachmentOptions={(user, group) => {
    const defaults = /* default actions from ChatConfigurator */;
    return defaults.filter((opt) => opt.id !== "attachment-file");
  }}
/>

"Show only message types that contain the word 'urgent'"

Tier 2 — messageRequestBuilder with .setSearchKeyword("urgent").

"Custom message type: a 'ping' message"

Tier 3b — create a CometChatMessageTemplate with category: "custom" + type: "ping", render a custom ContentView, send via CometChat.sendCustomMessage.

"Completely replace the kit's conversation-loading logic"

Tier 4b — DataSourceDecorator overriding getConversationsRequestBuilder() + possibly wrapping the fetch itself. Rare. Try Tier 2 first.


6. Anti-patterns

  1. Don't hand-roll a bubble when a template will do. CometChatMessageTemplate (Tier 3b) gives you full control over rendering + options without losing theming, reactions, typing, receipts.

  2. Don't post-filter a list's data after render. If you want "only online users," use Tier 2 usersRequestBuilder.setStatus("online") — don't fetch everyone then hide rows.

  3. Don't forget to remove listeners in useEffect cleanup. RN re-renders on every navigation can register duplicate listeners; each fires your handler once per registration.

  4. Don't collide listener IDs. Use APP_MESSAGE_LISTENER or ${componentName}_MESSAGE_LISTENER — constant, unique. Colliding IDs silently drop earlier registrations.

  5. Don't put CometChatTextFormatter instances in component state. Construct them once at module scope (or in a useMemo); re-creating them on every render loses the internal suggestion state.

  6. Don't fork or patch @cometchat/chat-uikit-react-native directly. Every customization should be possible via Tiers 1-4. Forking breaks on kit upgrades.

  7. Don't reach for Tier 4 before trying 1-3. DataSource decorators are powerful but fragile to kit internal changes. Props, request builders, and templates are stable surface area.

  8. Don't change component behavior via monkey-patching (e.g., Component.defaultProps = ...). Use the actual prop API. Monkey-patching is broken by design in React 19+.


7. Wiring a custom formatter end-to-end (full working example)

Say the user wants :emoji: shortcodes (e.g., :smile: → 😀):

// 1. Define the formatter — module scope, constructed once
import { CometChatTextFormatter } from "@cometchat/chat-uikit-react-native";
import { Text } from "react-native";

const EMOJI_MAP: Record<string, string> = {
  ":smile:": "😀", ":heart:": "❤️", ":thumbsup:": "👍", ":fire:": "🔥",
};

class EmojiShortcodeFormatter extends CometChatTextFormatter {
  constructor() {
    super();
    this.setRegexPatterns([/:[a-z_]+:/g]);
  }

  getFormattedText(input: string | null | React.ReactNode) {
    if (typeof input !== "string") return input;
    const parts = input.split(/(:[a-z_]+:)/g);
    return (
      <Text>
        {parts.map((p, i) =>
          EMOJI_MAP[p] ? <Text key={i}>{EMOJI_MAP[p]}</Text> : <Text key={i}>{p}</Text>,
        )}
      </Text>
    );
  }
}

// 2. Build the formatters array at module scope
import { CometChatMentionsFormatter, CometChatUrlsFormatter } from "@cometchat/chat-uikit-react-native";
export const TEXT_FORMATTERS = [
  new CometChatMentionsFormatter(),
  new CometChatUrlsFormatter(),
  new EmojiShortcodeFormatter(),
];

// 3. Wire into list + composer (same array — must match)
import { TEXT_FORMATTERS } from "./formatters";
<CometChatMessageList user={user} textFormatters={TEXT_FORMATTERS} hideReplyInThreadOption />
<CometChatMessageComposer user={user} textFormatters={TEXT_FORMATTERS} />

Skill routing reference

SkillWhen to route
cometchat-native-coreInit / login / provider chain
cometchat-native-componentsProp reference — which hide*, <Slot>View, *RequestBuilder is available (prerequisite for Tiers 1–2)
cometchat-native-placementWhere to put the customized components
cometchat-native-themingApp-wide color / typography / dark mode — Tier 1 alternative to style={{}}
cometchat-native-featuresWhich out-of-the-box features exist (so you know what needs customizing vs. what's already there)
cometchat-native-customizationThis skill — four-tier triage + custom formatters / templates / DataSource / events
cometchat-native-productionWhen customization depends on production auth (token refresh, user-ID mapping)
cometchat-native-troubleshootingFormatter doesn't apply, listener fires twice, slot view renders nothing, template not showing

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.