Skip to main content

Overview

Custom plugins let you handle any message type with your own rendering logic. This guide walks through building a complete “Location” plugin that:
  1. Renders a map preview bubble
  2. Provides context menu options
  3. Shows a preview in the conversations list

Step 1: Define the Plugin

Create a file for your plugin that implements CometChatMessagePlugin:
src/plugins/LocationPlugin.tsx
import React from "react";
import type { CometChat } from "@cometchat/chat-sdk-javascript";
import type {
  CometChatMessagePlugin,
  CometChatMessagePluginContext,
  CometChatMessageOption,
} from "@cometchat/chat-uikit-react";

// Extract location data from the custom message
function getLocationData(message: CometChat.BaseMessage) {
  const customMessage = message as CometChat.CustomMessage;
  const data = customMessage.getCustomData() as {
    latitude?: number;
    longitude?: number;
    address?: string;
  } | undefined;
  return {
    latitude: data?.latitude ?? 0,
    longitude: data?.longitude ?? 0,
    address: data?.address ?? "",
  };
}

export const LocationPlugin: CometChatMessagePlugin = {
  id: "location",
  messageTypes: ["location"],
  messageCategories: ["custom"],

  renderBubble(message: CometChat.BaseMessage, context: CometChatMessagePluginContext) {
    const { latitude, longitude, address } = getLocationData(message);
    const mapUrl = `https://maps.googleapis.com/maps/api/staticmap?center=${latitude},${longitude}&zoom=15&size=300x200&markers=${latitude},${longitude}&key=YOUR_API_KEY`;

    return (
      <div className="location-bubble">
        <img
          src={mapUrl}
          alt={address || "Location"}
          style={{ borderRadius: 8, width: "100%", maxWidth: 300 }}
        />
        {address && (
          <p style={{ margin: "8px 0 0", fontSize: 13, color: "#666" }}>
            {address}
          </p>
        )}
        <a
          href={`https://maps.google.com/?q=${latitude},${longitude}`}
          target="_blank"
          rel="noopener noreferrer"
          style={{ fontSize: 12, color: "#6851FF" }}
        >
          Open in Maps
        </a>
      </div>
    );
  },

  getOptions(
    message: CometChat.BaseMessage,
    context: CometChatMessagePluginContext
  ): CometChatMessageOption[] {
    const options: CometChatMessageOption[] = [];

    // Copy coordinates
    options.push({
      id: "copy-location",
      title: "Copy Location",
      onClick: (msg) => {
        const { latitude, longitude } = getLocationData(msg);
        void navigator.clipboard.writeText(`${latitude}, ${longitude}`);
        context.showToast?.("Location copied to clipboard");
      },
    });

    // Delete (sender only)
    options.push({
      id: "delete",
      title: context.getLocalizedString?.("delete") ?? "Delete",
      senderOnly: true,
      onClick: (msg) => context.onDeleteMessage?.(msg),
    });

    return options;
  },

  getLastMessagePreview(
    message: CometChat.BaseMessage
  ): string {
    const { address } = getLocationData(message);
    return address ? `📍 ${address}` : "📍 Location";
  },
};

Step 2: Register the Plugin

Pass your plugin to CometChatProvider:
src/App.tsx
import { CometChatProvider } from "@cometchat/chat-uikit-react";
import { LocationPlugin } from "./plugins/LocationPlugin";

function App() {
  return (
    <CometChatProvider
      appId="YOUR_APP_ID"
      region="us"
      authKey="YOUR_AUTH_KEY"
      uid="cometchat-uid-1"
      plugins={[LocationPlugin]}
    >
      <MyChatApp />
    </CometChatProvider>
  );
}
Your plugin is appended after the default plugins. Since resolution is first-match, default plugins handle their types first, and your plugin handles "location" messages.

Step 3: Send a Location Message

Use the CometChat SDK to send a custom message with type "location":
import { CometChat } from "@cometchat/chat-sdk-javascript";
import { CometChatUIKit } from "@cometchat/chat-uikit-react";

async function sendLocation(receiverUid: string, lat: number, lng: number) {
  const message = new CometChat.CustomMessage(
    receiverUid,
    CometChat.RECEIVER_TYPE.USER,
    "location",
    { latitude: lat, longitude: lng, address: "123 Main St" }
  );

  await CometChatUIKit.sendCustomMessage(message);
}

Step 4: Style the Bubble

Add CSS for your bubble:
src/plugins/LocationPlugin.css
.location-bubble {
  padding: 4px;
  max-width: 300px;
}

.location-bubble img {
  display: block;
  border-radius: 8px;
}

Plugin Interface Quick Reference

interface CometChatMessagePlugin {
  id: string;                    // Unique identifier
  messageTypes: string[];        // SDK message types to handle
  messageCategories: string[];   // SDK message categories to handle

  // Required
  renderBubble(message, context): ReactNode;

  // Optional
  getOptions?(message, context): MessageOption[];
  getLastMessagePreview?(message, loggedInUser, t?): string;
  getTextFormatters?(): CometChatTextFormatter[];

  // View slot overrides (optional)
  renderLeadingView?(message, context): ReactNode;
  renderHeaderView?(message, context): ReactNode;
  renderFooterView?(message, context): ReactNode;
  renderBottomView?(message, context): ReactNode;
  renderStatusInfoView?(message, context): ReactNode;
  renderReplyView?(message, context): ReactNode;
  renderThreadView?(message, context): ReactNode;
}

Tips

  • Lazy-load heavy components — use React.lazy() + Suspense for bubble components that import large libraries
  • Use context.getLocalizedString — for any user-facing text in options or bubbles
  • Return [] from getOptions — for system messages that shouldn’t have a context menu
  • Keep getLastMessagePreview short — max ~100 characters, plain text only (no HTML)
  • Test with messageTypes array — a single plugin can handle multiple types (like the Call Action plugin handles both audio and video in the call category)