| name | cometchat-features |
| description | Add features (calls, reactions, polls, file sharing, presence, etc.) to an already-integrated CometChat project. Routes to the right sub-flow based on feature type — default (already enabled), extension (API toggle), ai-feature (API toggle + OpenAI key), dashboard-only (third-party config), package-install (calls), or component-swap (rich text). |
| license | MIT |
| compatibility | Node.js >=18; @cometchat/chat-uikit-react ^6; integration must already be applied |
| metadata | {"author":"CometChat","version":"3.0.0","tags":"cometchat features extensions calls reactions polls ai-features"} |
Ground truth: the 5-tier feature catalog packages/registry/v6/features/catalog.json + the per-platform UI Kit defaultExtensions[]. (Official docs linked below.) Verify symbols against the installed package/source before relying on them.
Scope — this IS the web / React features skill (@cometchat/chat-uikit-react). Web/React intentionally has no separate "react-features" skill: this one is it. Each other family has its own features skill (native / angular / android-v5 / android-v6 / ios / flutter-v6). Flutter V5 has none — it proxies to cometchat-flutter-v6-features for enablement: feature enablement is the SDK/dashboard apply-feature path (identical across V5/V6); only the in-UI wiring differs, and V5 is legacy/maintenance-only. The apply-feature CLI + the 5-tier taxonomy below apply to all families.
Companion skills: cometchat-core covers initialization and the
provider pattern; cometchat-customization is the next step when a
feature is enabled but needs visual customization;
cometchat-troubleshooting handles post-feature-enable failures.
Purpose
This skill teaches Claude how CometChat features are structured and
what work is actually required to enable each one. Most features require
zero code — they are either already built into the UI Kit, toggled
via the cometchat apply-feature CLI (which hits the dashboard API),
or activated by a single npm install. Understanding which type a feature
is prevents unnecessary work.
1. Use this skill when
The user wants to add a specific feature to an already-integrated CometChat
project. Trigger phrases:
/cometchat features (or invoke the cometchat-features skill via your agent's mechanism — keyword "cometchat feature" or "add chat feature" works in most agents)
/cometchat features <name> (e.g. /cometchat features reactions)
/cometchat <feature> (e.g. /cometchat polls, /cometchat calls)
- "add reactions to my chat"
- "add video calling"
- "enable polls"
- "add file sharing"
- "enable smart replies"
- "add typing indicators"
2. Preconditions
The user must have an existing integration:
npx @cometchat/skills-cli info --json
If integrated is false, stop and tell the user to run /cometchat
first to create the integration.
3. The 5 feature categories (customer-facing) — and what work each needs
CometChat's canonical "Feature Availability" model sorts every feature into 5 categories by the work needed (the catalog encodes this per-feature as tier + the operative columns code / dashboard_settings / builder; run cometchat features info <id> --json).
Canonical public decision-reference (source of truth): Features & Extensions Guide on the public docs. It is the authoritative, always-current matrix of which integration method supports each feature (UI Kit / UI Kit Builder / Widget Builder / SDK), what dashboard setup it needs, and whether code is required. When the local catalog.json and this page disagree, the docs page wins — the catalog is a build-time snapshot derived from the same model and can lag a release. For any "can the builder do X? / is feature Y code-or-dashboard?" decision, consult this page (WebFetch it, or query the docs MCP) rather than answering from memory or a stale snapshot.
| Tier | Category | Work needed | Example |
|---|
| 1 | Core Messaging (Zero Setup) | Nothing — renders out of the box | Typing Indicators |
| 2 | Builder-Enabled (Config + UI Toggle) | Enable via Dashboard API; toggle/place in the UI Kit Builder | Polls |
| 3 | Config-Only (Dashboard-Driven) | Enable via Dashboard API; works automatically | Link Preview |
| 4 | Config + Settings (Smart) | Dashboard API + extra settings (API keys, thresholds) — no code | Smart Replies |
| 5 | SDK-Integrated (Config + Code) | Dashboard API + custom client code | Bitly |
Tiers 1–4 need no client code (code: "none"). Tier 5 needs code — and the implementation already lives in the docs. When a feature's code is custom-code or steps-in-docs (i.e. auto_wired_in_uikit: false), the flow is: flip the dashboard toggle (apply-feature <id>), then fetch the reference implementation from the docs (docs_topic, via the docs MCP) and adapt it — do NOT hand-roll. A few Core features carry code: "stitch-components" (e.g. Threaded Conversations, Advanced Search): no extension, but you compose existing kit components — the kit sample apps show the wiring (see [[uikit-local-clones-canonical-source]]). The 14 code-needed extensions: bitly, message-shortcuts, pin-message, rich-media-preview, save-message, tinyurl, voice-transcription, giphy, reminders, stipop, tenor, disappearing-messages, chatwoot, intercom.
T2 vs T3 is a Builder-surface nuance (does the Builder expose a placement/toggle for it). Operationally both are "enable via Dashboard, no code" — the builder: "toggle" flag marks the ones the Builder surfaces.
3b. Enablement mechanism (how the CLI flips each on)
Independently of the 5 customer-facing tiers above, the catalog tags each feature with an enablement mechanism — what the CLI's apply-feature actually does:
-
default (compiled-in): Shipped inside the UI Kit component bundle
unconditionally. CometChat builds reactions, typing indicators,
mentions, etc. into CometChatMessageList and CometChatMessageComposer
at compile time. The feature is always present; the only question is
whether the prop that surfaces it is enabled. No action needed.
-
extension (backend boolean toggle): Pure on/off backend extension.
The CLI's apply-feature command flips the toggle via the same REST
API the dashboard UI uses (POST /apps/{id}/extensions), so no
browser visit required. Once enabled, the UI Kit renders the matching
UI automatically only for the auto-wired subset (auto_wired_in_uikit: true,
code: "none"); the rest (tier 5) need client code from the docs. Examples: polls, link-preview,
message-translation, stickers.
-
ai-feature (backend AI toggle + OpenAI key): Same API path as
extensions but split out because the AI feature requires an OpenAI
API key on the app's AI settings (PUT /apps/{id}/ai/settings)
before the toggle (POST /apps/{id}/features/ai.{key}/enabled)
succeeds. The CLI handles both calls in one invocation when given
--openai-key sk-…. Examples: smart-replies, conversation-summary,
conversation-starter.
-
dashboard-only (third-party config): Requires entering config
the user has to fetch themselves — third-party API keys (Giphy,
Stipop, Tenor), webhooks (Chatwoot), or multi-field choices
(disappearing-messages interval, message-shortcuts list). The CLI
cannot automate these; the skill prints the dashboard path and
stops.
-
package-install (separate SDK): Voice/video calling requires
@cometchat/calls-sdk-javascript because it links against browser
media APIs that would bloat every integration if bundled unconditionally.
Once installed, the UI Kit detects it via dynamic import.
-
component-swap (variant component): Replaces one UI Kit component
with a drop-in variant whose default behavior differs (e.g.
CometChatCompactMessageComposer enables rich text by default).
The CLI does a safe word-boundary replace in state.files_owned.
Requires cometchat apply to have run (i.e. web/RN integrations
only — native cohorts can't use this path).
4. The feature catalog
Type 1 — Default features (~14, already enabled in UI Kit)
These are already part of the components your integration uses. The skill's
job is to tell the user they're already there and point at the relevant
component:
- Instant Messaging
- Media Sharing (file/image/audio/video)
- Read Receipts
- Mark as Unread
- Typing Indicator
- User Presence (online/offline)
- Reactions
- Mentions (incl. @all)
- Threaded Conversations
- Quoted Replies
- Group Chat
- Report Message
- Conversation/Advanced Search
For these: query the docs MCP for the feature's component/usage docs, show
the user where it is in their integration. No code changes needed.
Type 2a — Extensions (pure boolean, CLI-toggleable)
These are backend extensions enabled by a single API call (POST /apps/{id}/extensions).
Use the CLI — no browser visit required:
cometchat apply-feature <id>
For native cohorts (iOS / Android / Flutter / Angular) where there's no
.cometchat/state.json, pass --app-id explicitly:
cometchat apply-feature <id> --app-id <your-app-id>
Once enabled, the UI Kit auto-integrates them. No code changes needed — for the auto-wired subset.
⚠️ The "no code needed" pitch only holds for ~7 of 24 dashboard-toggle features (ENG-35721). The dashboard exposes 24 extensions; only the ones marked auto_wired_in_uikit: true in the CLI's catalog (~7 — Link Preview, Polls, Stickers, Message Translation, Smart Replies, Conversation Starter, and a handful more) render automatically via the kit's bubbles + composer. The other ~17 (Bitly, TinyURL, Voice Transcription, Reminders, Save Message, Pin Message, etc.) require either a kit prop opt-in OR a small component-side handler — apply-feature <id> flips the dashboard toggle but does NOT wire client-side rendering for those. Before promising "no code needed" to the customer, run cometchat features info <id> --json and check the auto_wired_in_uikit field; if false, set expectations honestly and emit the wiring code.
⚠️ features enable has TWO unstated prereqs (ENG-35721):
auth login must have completed — the toggle is gated on the dashboard bearer token. The CLI's "no app" error says nothing about login; running cometchat features enable <id> cold (after only provision setup) fails with a misleading message. Run cometchat auth status --json first; if "logged-out", run cometchat auth login before any features enable.
- Either
.cometchat/config.json OR --app-id <id> — the toggle needs an app context. If the user pasted credentials manually (the dispatcher's Step 2d path) they may NOT have a .cometchat/config.json yet. Manual .env users: run cometchat config save --app-id <id> --region <region> --json to bridge into .cometchat/config.json before apply-feature, OR pass --app-id on every apply-feature call.
Note: Conversation and Advanced Search has its own toggle on the
Features page. It is on by default but can be disabled. If a user
reports that search is missing, check this toggle.
Extensions — User Experience:
Avatar, Bitly, Link Preview, Message Shortcuts, Pin Message, Rich
Media Preview, Save Message, Thumbnail Generation, TinyURL, Voice
Transcription
Extensions — User Engagement:
Giphy, Message Translation, Polls, Reminders,
Stickers, Stipop, Tenor
⚠️ Broadcast was removed (ENG-35699). Earlier versions of this skill listed "Broadcast" here, but the CLI's 40-feature catalog has no broadcast or broadcast-message id — cometchat-skills-cli features info broadcast returns "not found." Broadcast as a use case is achievable via standard CometChat extensions (one-to-many group messages with subscribePresenceForAllUsers) or via Custom Messages — but there is no "Broadcast" extension toggle in the dashboard today. If a customer asks for broadcast functionality, route them at custom messages + a server-side fan-out webhook, not a feature toggle.
Extensions — Collaboration:
Collaborative Document, Collaborative Whiteboard
Extensions — Security:
Disappearing Messages, E2E Encryption (Enterprise plan only)
Extensions — Moderation (LEGACY — prefer Rules Management, see §"Moderation" below):
Data Masking, Image Moderation, Profanity Filter, Sentiment Analysis,
XSS Filter, Human Moderation, Report User, Slow Mode,
Virus/Malware Scanner. ⚠️ Deprecated; don't run alongside moderation Rules (double-processes every message).
Notifications extensions (Chat & Messaging → Features):
Email Notification, Push Notification, SMS Notification
Extensions — Customer Support:
Chatwoot, Intercom
Type 2b — AI features (CLI-toggleable, OpenAI key required)
smart-replies, conversation-summary, conversation-starter. These
need an OpenAI API key on the app's AI settings before the toggle
succeeds. The CLI does both calls in one invocation:
cometchat apply-feature smart-replies --openai-key sk-...
cometchat apply-feature smart-replies --app-id <id> --openai-key sk-...
After the first AI feature is enabled the key is stored on the app, so
subsequent ai-feature applies don't need --openai-key repeated.
Get an OpenAI key: https://platform.openai.com/api-keys
Type 2c — Dashboard-only (third-party config required)
These extensions require config the user has to provide themselves
(third-party API keys / webhooks / multi-field setup) — apply-feature
returns manual-action-required and prints the dashboard path. The
CLI cannot automate these:
- Third-party API keys: Giphy, Stipop, Tenor, Intercom
- Webhooks: Chatwoot
- Multi-field config: Message Shortcuts (shortcut list), Disappearing Messages (interval)
Manual flow for these:
- https://app.cometchat.com → select your app
- Sidebar → Chat & Messaging → Features
- Find the extension, enter the third-party config, toggle ON
Moderation — use Rules Management (NOT the legacy extensions)
The canonical moderation system is Rules Management, not the old per-extension
toggles. Configure rules once in the Dashboard (Moderation → Settings → Rules,
or the /moderation/rules REST API — also /moderation/{keywords,reasons, blocked-messages,flagged-messages,reviewed-messages}) and they auto-apply to every
message with no client code (the UI Kit + SDK enforce them seamlessly). Docs:
moderation/overview + moderation/rules-management.
⚠️ Do NOT run legacy moderation extensions AND Rules on the same app. The
legacy extensions (Data Masking, Image Moderation, Profanity Filter, Sentiment
Analysis, XSS Filter, Slow Mode, Virus/Malware Scanner) are deprecated. If
both a legacy extension and a Rule are active, every message is processed
twice → delays + perf issues. Disable the legacy extensions (Dashboard →
Extensions) before creating Rules. apply-feature <legacy-id> still works but
emits this same warning — prefer Rules.
- Notifications (separate, unaffected): Email, Push, SMS — Sidebar →
Extensions → configure + enable.
After enabling any feature, run cometchat verify to ensure the
existing integration still passes. No code changes are needed — the
UI Kit picks up enabled features automatically.
Type 3 — Package-install features (4, calls)
These require installing @cometchat/calls-sdk-javascript. Once installed,
the UI Kit auto-detects it and surfaces the call UI in CometChatMessageHeader,
CometChatConversations, etc.
- Call Buttons (in message headers)
- Incoming Call notifications
- Outgoing Call interface
- Call Logs (call history)
For these, the user opting in IS consent — run the install directly:
npm install @cometchat/calls-sdk-javascript@^5
npx @cometchat/skills-cli verify --json
The UI Kit's initiateAfterLogin() auto-calls enableCalling() after the
package is installed. No manual wiring needed for default call buttons in
CometChatMessageHeader. Restart the dev server.
Type 5 — Custom message types (APPEND, never REPLACE — ENG-35706)
When a customer wants to send a new message type (location, product card, custom event), the V6 React kit ships a templates API that is dangerously easy to misuse. Two testers hit the same trap: they added one custom template and watched ALL existing bubbles (text, image, audio, video, file) disappear AND the composer's entire attachment menu (Camera/Image/Video/File/Whiteboard/Document) get replaced by their one new attachment.
Cause: <CometChatMessageList templates={[locationTemplate]} /> and <CometChatMessageComposer attachmentOptions={[locationOption]} /> REPLACE the kit's built-in templates / options rather than appending to them.
Correct pattern — always merge with the kit's defaults:
import {
CometChatMessageList,
CometChatMessageComposer,
CometChatMessageTemplate,
CometChatMessageComposerAction,
CometChatUIKit,
CometChatUIKitConstants,
} from "@cometchat/chat-uikit-react";
const locationTemplate = new CometChatMessageTemplate({
type: "location",
category: CometChatUIKitConstants.MessageCategory.custom,
contentView: (message, alignment) => <LocationBubble message={message} />,
});
const defaultTemplates = CometChatUIKit.getDataSource().getAllMessageTemplates();
const mergedTemplates = [...defaultTemplates, locationTemplate];
<CometChatMessageList templates={mergedTemplates} />
Same rule for attachment options. getAttachmentOptions takes a ComposerId ({ user?, group?, parentMessageId? }) AND a required second argument (additionalConfigurations), then append your action:
const composerId = {
parentMessageId: null,
user: selectedUser?.getUid() ?? null,
group: selectedGroup?.getGuid() ?? null,
};
const additionalConfigurations = { messageToReplyRef: { current: null } };
const defaultAttachments = CometChatUIKit.getDataSource().getAttachmentOptions(
composerId,
additionalConfigurations,
);
const mergedAttachments = [...defaultAttachments, new CometChatMessageComposerAction({
id: "send-location",
title: "Location",
iconURL: "/icons/location.svg",
onClick: () => { },
})];
<CometChatMessageComposer attachmentOptions={mergedAttachments} />
Also: when sending the custom message, set both receiverType AND type correctly. Two testers hit "Cannot determine message recipient" because they passed a partial CustomMessage constructor. Use the full form:
const msg = new CometChat.CustomMessage(
receiverID,
receiverType,
"location",
{ lat, lng, label }
);
CometChat.sendCustomMessage(msg);
Custom type sends + renders live, but vanishes on reload? The custom message
appears immediately (the ccMessageSent event), but to fetch it from history
you must include your custom category + type in the list's messagesRequestBuilder.
Extend the kit's defaults (don't hand-list them) — append to
getAllMessageCategories() + getAllMessageTypes():
const categories = CometChatUIKit.getDataSource().getAllMessageCategories();
if (!categories.includes(CometChatUIKitConstants.MessageCategory.custom))
categories.push(CometChatUIKitConstants.MessageCategory.custom);
const types = CometChatUIKit.getDataSource().getAllMessageTypes();
if (!types.includes("location")) types.push("location");
const messagesRequestBuilder = new CometChat.MessagesRequestBuilder()
.setCategories(categories).setTypes(types).hideReplies(true).setLimit(30);
<CometChatMessageList templates={mergedTemplates} messagesRequestBuilder={messagesRequestBuilder} />
(Verified against the official v6 sample app's live-location feature.) Tip: on the
CustomMessage, call setConversationText("Live Location") so it reads nicely as
the conversation-list preview, and setSender(loggedInUser) for optimistic render.
Override an existing message type's bubble (text / image / etc.)
Same templates API — fetch all, replace contentView (or bubbleView for the whole bubble) on the matching template, pass the array. There is NO separate "override" call; you reuse getAllMessageTemplates():
const templates = CometChatUIKit.getDataSource().getAllMessageTemplates();
const withCustomTextBubble = templates.map((t) => {
if (
t.type === CometChatUIKitConstants.MessageTypes.text &&
t.category === CometChatUIKitConstants.MessageCategory.message
) {
t.contentView = (message, alignment) => <MyTextBubble message={message} />;
}
return t;
});
<CometChatMessageList templates={withCustomTextBubble} />
(CometChatUIKit.getDataSource().getMessageTemplate(type, category) returns a single template if you'd rather fetch just one — but you still pass the full array to the templates prop.)
Add a custom message option (Forward-style long-press action)
Message options live on each template's options function. Override it and merge with the kit defaults (same REPLACE-vs-APPEND rule) via getMessageOptions. Custom actions use CometChatActionsIcon (NOT CometChatMessageComposerAction — that's composer-only):
import { CometChatActionsIcon } from "@cometchat/chat-uikit-react";
const templates = CometChatUIKit.getDataSource().getAllMessageTemplates();
const withForward = templates.map((t) => {
if (t.type === CometChatUIKitConstants.MessageTypes.text) {
t.options = (loggedInUser, message, group) => {
const options = CometChatUIKit.getDataSource().getMessageOptions(loggedInUser, message, group);
options.push(
new CometChatActionsIcon({
id: "forward",
title: "Forward",
iconURL: "/icons/forward.svg",
onClick: (id) => { },
})
);
return options;
};
}
return t;
});
<CometChatMessageList templates={withForward} />
"Forward" is not a built-in kit option, so it's added as a custom CometChatActionsIcon. The built-ins (reply, edit, delete, react, …) come from getMessageOptions — keep them by merging, don't return only your action.
⚠️ Kit-side limitation (ENG-35706): custom message send sometimes replaces an existing message in the list in real-time (UI-side overwrite, not server-side). No client workaround today; file a kit ticket if you observe this and document for the customer.
Text formatters (inline text styling — mentions, URLs, custom @/#/! tokens)
Text formatters transform message text inline. The kit ships CometChatMentionsFormatter, CometChatUrlsFormatter, CometChatMarkdownFormatter (all subclasses of the base CometChatTextFormatter). Same APPEND-not-REPLACE rule as templates/options: passing a bare textFormatters={[...]} array drops the built-ins — start from getAllTextFormatters({}) and append. (Pattern verified against the kit sample app sample-app/src/components/CometChatMessages/CometChatMessages.tsx.)
import {
CometChatUIKit,
CometChatMessageList,
CometChatTextFormatter,
} from "@cometchat/chat-uikit-react";
const formatters: CometChatTextFormatter[] =
CometChatUIKit.getDataSource().getAllTextFormatters({});
<CometChatMessageList textFormatters={formatters} />;
Guides (canonical subclass examples): custom-text-formatter-guide, mentions-formatter-guide, url-formatter-guide, shortcut-formatter-guide — all under ui-kit/react/ (query the docs MCP, or documentation/docs/ui-kit/react/*-formatter-guide.mdx locally). Each shows a complete CometChatTextFormatter subclass (regex + getFormattedText + a custom view).
Type 6 — Dashboard-AI mode (UI-side side-effects — ENG-35706)
When the customer enables the AI Agent on the dashboard (e.g. aiAssistant: true for an app), the SAME <CometChatMessageList> renders with thread reply buttons + call buttons that don't make sense in an AI-only conversation surface.
Detect AI mode and branch the layout:
const isAiMode = aiAssistantConfig?.enabled === true;
<CometChatMessageList
user={selectedUser}
group={selectedGroup}
// AI conversations shouldn't show "Reply in Thread" / "Message Privately" / call buttons
hideReplyInThreadOption={true}
// (call buttons live on CometChatMessageHeader, NOT MessageList — hide them there)
/>
<CometChatMessageHeader
user={selectedUser}
hideVideoCallButton={isAiMode}
hideVoiceCallButton={isAiMode}
/>
The kit doesn't auto-detect dashboard AI mode and adjust its layout — that's a kit-side gap (filed as part of ENG-35706). Until the kit ships a built-in aiMode prop, the skill must read the dashboard flag via features info ai-assistant --json (see ENG-35716 dispatcher rule) and gate the relevant buttons.
Type 4 — Component-swap features (drop-in variant)
Some features require swapping one component for a variant that has
different default behavior. The CLI handles the swap automatically —
it walks state.files_owned, performs a word-boundary regex replace,
updates state.json checksums, and records the applied feature so
re-runs are no-ops. Idempotent.
Currently available:
rich-text-formatting — swaps CometChatMessageComposer →
CometChatCompactMessageComposer (the compact variant enables rich
text formatting by default; the regular composer has
enableRichTextEditor=false baked in)
npx @cometchat/skills-cli apply-feature rich-text-formatting
Do NOT hand-edit the swap. The CLI is the source of truth. If future
SDK releases add new variant components, they will follow this same
apply-feature <id> pattern.
4b. Deep patterns for three most-requested features
For calls, AI smart replies, and presence, the catalog above only says "install a package" or "toggle in dashboard." Here are the concrete compositional patterns so common requests don't require a docs MCP round-trip.
Calls (audio + video)
After npm install @cometchat/calls-sdk-javascript, call buttons auto-appear in CometChatMessageHeader and the call UI renders in place. No manual wiring needed for basic 1:1 audio/video calls.
For custom integration — e.g. putting a "Start video call" button outside the message header, or handling an incoming call notification in a custom way — use CometChatCallButtons + CometChatIncomingCall + CometChatOngoingCall:
import { useState, useEffect } from "react";
import {
CometChatCallButtons,
CometChatIncomingCall,
CometChatOngoingCall,
} from "@cometchat/chat-uikit-react";
import { CometChat } from "@cometchat/chat-sdk-javascript";
export function CustomCallUI({ targetUser }: { targetUser: CometChat.User }) {
const [ongoingCall, setOngoingCall] = useState<CometChat.Call>();
useEffect(() => {
const listenerId = "custom-call-listener";
CometChat.addCallListener(
listenerId,
new CometChat.CallListener({
onOutgoingCallAccepted: (call: CometChat.Call) => setOngoingCall(call),
onIncomingCallCancelled: () => setOngoingCall(undefined),
onCallEndedMessageReceived: () => setOngoingCall(undefined),
}),
);
return () => CometChat.removeCallListener(listenerId);
}, []);
return (
<>
<CometChatCallButtons user={targetUser} />
<CometChatIncomingCall />
{ongoingCall && <CometChatOngoingCall call={ongoingCall} />}
</>
);
}
Common gotchas — UI Kit path:
- Calls require a logged-in CometChat user on both sides. Test from two browsers (or incognito) logged in as different UIDs.
CometChatIncomingCall must be mounted globally (e.g. in your provider or layout) so incoming calls ring on every page.
- Group calls use
CometChat.Group instead of CometChat.User on CometChatCallButtons.
Common gotchas — SDK-only path (no UI Kit components, ENG-35707 Birendra follow-up):
When the integrator opts out of <CometChatCallButtons> / <CometChatIncomingCall> / <CometChatOngoingCall> and builds the call surface directly on the Calls SDK, the UI-Kit gotchas above don't apply but a different set kicks in:
CometChat.CALL_TYPE.AUDIO vs .VIDEO (Chat SDK) are NOT the same as CometChatCalls.constants.TYPE.VOICE / .VIDEO (Calls SDK) — use Chat SDK enum on the Call entity, Calls SDK enum on CallSettings. See cometchat-react-calls §4c constants table.
setIsAudioOnlyCall(true) on CallSettingsBuilder for voice calls — without it, voice calls still acquire the camera (just don't render it).
joinSession MUST fire after the container is mounted — don't call it inside the onOutgoingCallAccepted listener directly; set state and let a useEffect(phase, containerRef) join when the ref + phase agree. The natural site fires before the in-call panel renders.
- Group calls have no "ring a group" primitive — use a CustomMessage broadcast + a shared sessionId; the UI Kit's
CometChatCallButtons for groups sends a meeting-type CustomMessage, not initiateCall. SDK-only mirrors that.
- Default call types — 1:1 = video, group = voice (mirroring the kit's defaults). If you build custom buttons, replicate this so customers' integrations behave consistently.
CometChatCalls.login is mandatory on raw-SDK paths (NOT when using the UI Kit, where enableCalls = true / kit-managed init auto-logs the Calls SDK). On raw SDK, after Chat SDK login, separately call CometChatCalls.login(uid, apiKey) — without it, generateToken returns 401.
These map to the React-specific recipes in cometchat-react-calls §4c. Same primitives apply across families; container-mount race + ref-state coordination is generic.
AI smart replies
Smart replies is an ai-feature. Enable with one CLI call (the first time also sets the OpenAI key on the app):
cometchat apply-feature smart-replies --openai-key sk-...
No code changes are required — the CometChatMessageComposer automatically renders suggested replies as chips above the input when there's a recent incoming message.
For a custom UI — e.g. showing smart replies inline instead of above the composer, or only for certain conversation types — you read the extension data from the incoming message and render your own chips:
function SmartReplyChips({ message }: { message: CometChat.BaseMessage }) {
const metadata = message.getMetadata() as Record<string, unknown> | undefined;
const extensions = (metadata?.["@injected"] as Record<string, unknown>)?.["extensions"] as
| Record<string, unknown>
| undefined;
const smartReply = extensions?.["smart-reply"] as { reply_positive?: string; reply_neutral?: string; reply_negative?: string } | undefined;
if (!smartReply) return null;
const replies = [smartReply.reply_positive, smartReply.reply_neutral, smartReply.reply_negative].filter(Boolean) as string[];
return (
<div style={{ display: "flex", gap: 8, padding: 8 }}>
{replies.map((r) => (
<button key={r} onClick={() => sendTextMessage(r)}>{r}</button>
))}
</div>
);
}
Smart replies are server-generated and attached to messages via the @injected.extensions.smart-reply metadata path — the AI feature runs on CometChat's backend, not in your code.
Presence (online / offline status)
Presence is a default feature (Type 1) — online status indicators appear automatically on user avatars in CometChatConversations, CometChatUsers, and CometChatGroupMembers. Nothing to install, nothing to enable.
For custom UI that needs to know a specific user's online state — e.g. a "Sold by Aria Chen · online now" label on a product page — subscribe to user events:
import { useEffect, useState } from "react";
import { CometChat } from "@cometchat/chat-sdk-javascript";
export function useUserPresence(uid: string): "online" | "offline" | "unknown" {
const [status, setStatus] = useState<"online" | "offline" | "unknown">("unknown");
useEffect(() => {
CometChat.getUser(uid).then((u) => {
setStatus(u.getStatus() === "online" ? "online" : "offline");
});
const listenerId = `presence-${uid}`;
CometChat.addUserListener(
listenerId,
new CometChat.UserListener({
onUserOnline: (user: CometChat.User) => {
if (user.getUid() === uid) setStatus("online");
},
onUserOffline: (user: CometChat.User) => {
if (user.getUid() === uid) setStatus("offline");
},
}),
);
return () => CometChat.removeUserListener(listenerId);
}, [uid]);
return status;
}
Common gotchas:
- Presence events only fire for users the current user has interacted with (conversation exists, in same group, etc.). For arbitrary UIDs with no prior interaction, you may need to call
CometChat.getUser(uid) periodically instead.
getStatus() returns "online" or "offline" — also check getLastActiveAt() for a "last seen X ago" timestamp.
- "Last seen" is disabled by default on free-tier apps. Enable it in the dashboard (Settings → Chat → Last Seen).
5. Docs lookup contract
For any feature question not in our local catalog (cometchat features info), the canonical CometChat docs are the source of truth for:
- Feature/method availability matrix — Features & Extensions Guide: which of UI Kit / UI Kit Builder / Widget Builder / SDK supports each feature, dashboard setup, and code-required flags. This page outranks the local
catalog.json snapshot when they disagree.
- Per-feature SDK reference (props, callbacks, builders, events)
- Per-feature configuration details beyond the dashboard path above
- Feature compatibility notes (which features need backend setup,
which auto-wire, which require explicit
setExtensions([...]))
The docs MCP at cometchat-docs is the best way to query them when
available, but it is not a hard requirement.
Hard rules:
- Look up the docs before answering any feature question that's not
in our local catalog. Never invent feature config from training-data
memory. Use whichever lookup path is available, in order:
- (a) docs MCP — query the
cometchat-docs MCP tool if your agent
has it. Richest path.
- (b) install the MCP, if your agent supports it — Claude Code:
claude mcp add --transport http cometchat-docs https://www.cometchat.com/docs/mcp. Other agents (Cursor, Codex,
Cline, …) configure MCP their own way, or not at all — do NOT block.
- (c) fetch/search the public docs — same content at the canonical
URLs below, or web-search
site:cometchat.com/docs. Universal
fallback; never STOP and dead-end the user when the MCP isn't
installed — fall through to (c).
- Use
cometchat apply-feature <id> for extension and ai-feature
types. The CLI is the canonical path. Only fall back to the
dashboard URL when the CLI returns manual-action-required,
auth-required, or error.
- Canonical reference URLs (use as starting points if the agent
doesn't have an MCP query handy):
6. Steps
Step 1 — Read state
npx @cometchat/skills-cli info --json
If not integrated, stop. Otherwise note the framework + experience so you
can find the right files.
Step 2 — Determine feature
If the user named a feature, use it. Otherwise list the categories above
and ask which feature they want.
Step 3 — Classify the feature
Match the feature name against the 4 types in section 4. If you don't know
the type, query the docs MCP first.
Step 4 — Execute the right sub-flow
-
Default: show the user it's already there. Point at the component.
Use npx @cometchat/skills-cli features info <id> to surface
the walkthrough verbatim.
- CRITICAL — if the user explicitly wants a UI element to surface
the default feature (e.g. "implement conversation search",
"add a search bar", "show typing indicators in the header",
"expose mentions in the composer"), do NOT add a new component
yet. Most default features are exposed via PROPS on the
components your integration already mounts:
- "search bar" →
showSearchBar on CometChatConversations
(and onSearchBarClicked to swap in <CometChatSearch> for
advanced dual-scope search if the user wants that)
- "filter conversations / messages" →
conversationsRequestBuilder
/ messagesRequestBuilder
- "custom empty / error / loading state" →
emptyView,
errorView, loadingView on the list components (verified vs the v6
React kit; the *StateView form is only on CometChatNotificationFeed)
- "custom message bubble" →
templates prop on
CometChatMessageList (NOT a custom bubble component)
- "hide / disable a sub-feature" →
disable* boolean props
- "click handler" →
onItemClick, onMessageClick,
onSearchBarClicked, onBack
- "custom subtitle / status / timestamp" →
subtitleView,
statusView, timestampView
Process before any code change:
- Read the files in
.cometchat/state.json files_owned and
grep for the <CometChat[A-Z] JSX components actually in use:
grep -hoE '<CometChat[A-Z][a-zA-Z]*' \
$(jq -r '.files_owned[]' .cometchat/state.json 2>/dev/null) \
2>/dev/null | sort -u
- Query the docs MCP for
"<ComponentName> props" for each one.
- If a prop matches the user's intent, add the prop and stop.
No new components, no custom CSS, no new files.
- Only if no prop matches, route to the
cometchat-customization
skill for the full four-tier discovery.
-
Extension / AI feature: run cometchat apply-feature <id>. The
CLI hits the dashboard API directly using the bearer token from
cometchat auth login (stored in the OS keychain). For native
cohorts (iOS / Android / Flutter / Angular), pass --app-id <id>
explicitly because there's no .cometchat/state.json. AI features
also take --openai-key sk-… the first time:
cometchat apply-feature polls
cometchat apply-feature smart-replies --openai-key sk-...
cometchat apply-feature polls --app-id A1B2C3
cometchat apply-feature smart-replies --app-id A1B2C3 --openai-key sk-...
Response shapes (--json):
"status": "applied" → done. Tell the user to hard-refresh
(Cmd+Shift+R) the browser tab running their dev server.
"status": "already-applied" → the feature is already enabled
in this integration's state.applied_features.
"status": "auth-required" → run cometchat auth login first.
"status": "openai-key-required" (ai-feature only) → re-run
with --openai-key sk-….
"status": "error" → surface next_steps verbatim.
Only fall back to the dashboard when the CLI returns error or
isn't available. Manual flow for extensions: app.cometchat.com →
Chat & Messaging → Features → flip Status toggle.
Note: if the feature has auto_wired_in_uikit: false in the
catalog (most non-default extensions), the toggle alone isn't
enough — you also need to register the extension via
UIKitSettingsBuilder.setExtensions([...]) before init. Query
the docs MCP for the exact builder syntax.
-
Dashboard-only: the CLI returns manual-action-required and
prints the dashboard path. These need third-party config (Giphy
API key, Chatwoot webhook, etc.) that only the user can supply.
Walk them through the dashboard.
-
Package-install (calls): run npm install @cometchat/calls-sdk-javascript
directly. The user opted in, that IS consent.
-
Component-swap: run cometchat apply-feature <id> (web/RN
only — needs state.json). The CLI handles the swap
deterministically. Do NOT hand-edit.
Step 5 — Verify
npx @cometchat/skills-cli verify --json
Surface any failed checks verbatim. If anything looks off after enabling
a feature (drift, unexpected build error, env warning), run
cometchat doctor for combined drift + env + AST diagnostics with
per-issue fix instructions, or route to the cometchat-troubleshooting
skill for deeper triage.
Hard rules
- Never modify a project without an existing CometChat integration.
- Always query the docs MCP for SDK reference (do not invent function names).
- For component-swap, extension, and ai-feature types, always use
cometchat apply-feature <id> — the CLI is the source of truth,
never hand-edit and never tell the user to navigate the dashboard
unless the CLI itself returns manual-action-required.
- For ai-feature types, the OpenAI key prerequisite is the only
manual input — pass it as
--openai-key sk-… the first time per
app.
- For native cohorts (iOS / Android / Flutter / Angular), always
pass
--app-id <id> because their projects don't write
.cometchat/state.json.
- For package-install features (calls), the user opting in IS consent —
run
npm install <package> directly.
- For dashboard-only features (third-party API keys, webhooks,
multi-field config), walk the user through the dashboard — these
cannot be automated.
- Always use
npx @cometchat/skills-cli.
Sources