Skip to main content
Card messages let your app display rich, structured content — such as product cards, confirmations, or AI-generated summaries — inside a conversation. In the JavaScript SDK, card support is receive-only: the SDK deserializes incoming cards and hands you their raw payload through typed accessors, and your app is responsible for turning that payload into UI. The body of a card is Card Schema JSON — the same structured layout used by Campaigns notification feeds. Cards are authored server-side (they cannot be composed or sent from the SDK) via the Platform (REST) API or the Dashboard Bubble Builder, and delivered to clients like any other message.
The SDK never interprets the body of a card — every accessor returns the raw Card Schema JSON exactly as it was sent. Drawing it is your app’s responsibility: pass that JSON to the CometChat Cards renderer for your framework.

How Cards Arrive

There are three ways a card can reach your app. Which one you handle depends on where the card originates.
DeliveryListener / CallbackObjectWhen to use
Standalone card messageMessageListener.onCardMessageReceivedCardMessageA card sent as a complete message on its own
Inline card in an AI agent replyMessageListener.onAIAssistantMessageReceivedAIAssistantMessagegetElements()A card embedded in a persisted AI Agent reply
Real-time card streaming eventsAIAssistantListener.onAIAssistantEventReceivedAIAssistantCardStartedEvent / ...ReceivedEvent / ...EndedEventProgressive rendering of a card while an AI Agent run streams
The sections below cover each of these in turn.

Standalone Card Messages

A standalone card arrives as a message whose category is card (its type is text). The SDK deserializes it into a CardMessage and delivers it through the onCardMessageReceived callback of the MessageListener.
Branch on getCategory() === "card" to identify a card message — getType() returns "text", the same as a plain text message.
CardMessage extends BaseMessage, so all the usual metadata (getSender(), getReceiver(), getSentAt(), getConversationId(), …) is available. It adds these card-specific accessors:
GetterReturn TypeDescription
getCard()Object | undefinedThe raw Card Schema JSON object (data.card). Returns undefined when no card is present.
getText()stringThe plain-text content that accompanies the card (data.text), used as preview text in the conversation list and push notifications.
getFallbackText()stringText to display when the card cannot be rendered (card.fallbackText).
getTags()Array<String>Tags associated with the message.

Receiving a Card Message

Register a MessageListener and implement onCardMessageReceived:
const listenerID: string = "UNIQUE_LISTENER_ID";

CometChat.addMessageListener(
  listenerID,
  new CometChat.MessageListener({
    onCardMessageReceived: (cardMessage: CometChat.CardMessage) => {
      console.log("Card message received", cardMessage);
      // getCard() returns the raw Card Schema JSON — pass it to your card renderer.
      const card = cardMessage.getCard();
    },
  })
);

Rendering a Card Message

getCard() returns the raw Card Schema JSON — turning it into UI is your app’s job. Pass that JSON to the CometChat Cards renderer for your framework (see the note at the top of this page), and fall back to getFallbackText() — then getText() — when the card is absent or cannot be rendered. A card payload is arbitrary JSON. A typical shape looks like this — but treat the structure as opaque and render whatever fields your platform sends:
{
  "text": "Here is your order summary",
  "card": {
    "fallbackText": "Order #1234 — 2 items — $48.00",
    "title": "Order #1234",
    "components": []
  }
}

Inline Cards in AI Agent Replies

When an AI Agent produces a card, it is not delivered as a separate message. Instead it is embedded, in order, inside the agent’s persisted reply — an AIAssistantMessage received through the existing onAIAssistantMessageReceived callback. The ordered content of an assistant reply is exposed through getElements(), which returns an array of AIAssistantElement objects. Each element is a { type, value } envelope:
GetterReturn TypeDescription
getType()stringThe element type — "text", "card", or any other type your platform emits.
getData()anyThe element’s raw body. For "text" it is the text string; for "card" it is { card, cardId }.
When getElements() is non-empty, prefer it as the render source and walk the blocks in order — a card-only reply may have an empty getAssistantMessageData().getText(), so relying on the text alone can drop content. getElements() returns an empty array only for messages that predate this feature (or that carry no elements); in that case fall back to getAssistantMessageData().getText(). The elements API is purely additive and never breaks existing rendering.

Rendering an Assistant Reply with Elements

Walk the elements in order and branch on getType():
CometChat.addMessageListener(
  "UNIQUE_LISTENER_ID",
  new CometChat.MessageListener({
    onAIAssistantMessageReceived: (message: CometChat.AIAssistantMessage) => {
      const elements: CometChat.AIAssistantElement[] = message.getElements();

      if (elements.length === 0) {
        // Older message with no elements — fall back to the plain text reply.
        console.log("Message text:", message.getAssistantMessageData().getText());
        return;
      }

      elements.forEach((element: CometChat.AIAssistantElement) => {
        switch (element.getType()) {
          case "text":
            console.log("Text block:", element.getData()); // a string
            break;
          case "card": {
            const { card, cardId } = element.getData(); // { card, cardId }
            console.log("Card block:", cardId);
            // Pass the card to your card renderer.
            break;
          }
          default:
            console.log("Unknown element type:", element.getType());
            break;
        }
      });
    },
  })
);

Real-Time Card Streaming Events

While an AI Agent run is in progress, cards are streamed as real-time events over the AIAssistantListener so you can render (or show a placeholder for) a card before the persisted AIAssistantMessage arrives. These events are delivered through onAIAssistantEventReceived alongside the other AI Agent stream events. There are three card events, emitted in order per card:
OrderEvent typeObjectMeaning
1card_startAIAssistantCardStartedEventThe agent began generating a card
2cardAIAssistantCardReceivedEventThe full card payload is available
3card_endAIAssistantCardEndedEventThe card is finalized
Every card event extends AIAssistantBaseEvent (so it carries getType(), getConversationId(), getRunId(), etc.) and adds these card-specific getters:
EventExtra GettersDescription
AIAssistantCardStartedEventgetStreamMessageId(), getCardId(), getExecutionText()Stream/card IDs and the text shown while the card is generated
AIAssistantCardReceivedEventgetStreamMessageId(), getCardId(), getCard()Stream/card IDs and the raw card payload
AIAssistantCardEndedEventgetStreamMessageId(), getCardId()Stream/card IDs marking the end of the card
Use getStreamMessageId() / getCardId() to correlate the three events for the same card, and getCard() on the card event to obtain the payload to render.
const listenerID: string = "UNIQUE_LISTENER_ID";

CometChat.addAIAssistantListener(listenerID, {
  onAIAssistantEventReceived: (event: CometChat.AIAssistantBaseEvent) => {
    switch (event.getType()) {
      case "card_start": {
        const started = event as CometChat.AIAssistantCardStartedEvent;
        console.log("Card generation started:", started.getCardId());
        console.log("Execution text:", started.getExecutionText());
        break;
      }
      case "card": {
        const received = event as CometChat.AIAssistantCardReceivedEvent;
        console.log("Card received:", received.getCardId());
        const cardPayload = received.getCard();
        // Pass cardPayload to your card renderer.
        break;
      }
      case "card_end": {
        const ended = event as CometChat.AIAssistantCardEndedEvent;
        console.log("Card generation ended:", ended.getCardId());
        break;
      }
      default:
        // Other agent stream events (run/tool/text) — handle as needed.
        break;
    }
  },
});
Streaming card events are the live view of a card as it is produced. Once the run finishes, the same card is persisted inside the assistant reply and reaches you again as an element on the AIAssistantMessage (see Inline Cards in AI Agent Replies). Correlate them by cardId to avoid rendering the card twice.

Removing Listeners

Always remove listeners when they are no longer needed (for example, on component unmount or page navigation) to prevent memory leaks and duplicate handling.
CometChat.removeMessageListener("UNIQUE_LISTENER_ID");
CometChat.removeAIAssistantListener("UNIQUE_LISTENER_ID");

Next Steps

Receive a Message

Handle real-time, unread, and historical messages of every type

AI Agents

Handle AI Agent runs, streaming events, and persisted agent replies

Real-Time Listeners

See every callback on the MessageListener and AIAssistantListener

Message Reference

Full class reference for CardMessage, AIAssistantElement, and card events