This is beta documentation for Stream Chat React SDK v14. For the latest stable version, see the latest version (v13) .

Message Actions

MessageActions renders the actions available for a message. Even though this page still lives under experimental-features in the docs tree, the component is part of the main stream-chat-react package:

import { MessageActions } from "stream-chat-react";

Best Practices

  • Start from defaultMessageActionSet unless you need a fully custom action surface.
  • Keep custom action components small and read message state from hooks like useMessageContext().
  • Use WithComponents to scope action overrides to the part of the app that needs them.
  • Define a custom messageActionSet explicitly if your app depends on a stable action order.
  • Re-check custom actions in both channel and thread message lists, because the SDK filters some actions differently across those contexts.

How MessageActions Works

An action-set item has:

  • a type
  • a placement of either quick or dropdown
  • a Component

Quick actions render inline next to the message. Dropdown actions render inside the ContextMenu opened by the actions button.

The exported defaultMessageActionSet already includes the default SDK actions and ordering. In most cases, start from that set and filter or extend it.

Basic Customization

This example removes the default delete action and adds one custom dropdown action.

import {
  type ContextMenuItemProps,
  Channel,
  ChannelHeader,
  MessageActions,
  MessageInput,
  MessageList,
  Thread,
  Window,
  WithComponents,
  defaultMessageActionSet,
  useMessageContext,
} from "stream-chat-react";

const BookmarkAction = ({ closeMenu }: ContextMenuItemProps) => {
  const { message } = useMessageContext();

  return (
    <button
      onClick={() => {
        saveMessage(message.id);
        closeMenu();
      }}
    >
      Save for later
    </button>
  );
};

const CustomMessageActions = () => {
  const messageActionSet = [
    ...defaultMessageActionSet.filter(({ type }) => type !== "delete"),
    {
      Component: BookmarkAction,
      placement: "dropdown" as const,
      type: "bookmark",
    },
  ];

  return <MessageActions messageActionSet={messageActionSet} />;
};

const App = () => (
  <WithComponents overrides={{ MessageActions: CustomMessageActions }}>
    <Channel>
      <Window>
        <ChannelHeader />
        <MessageList />
        <MessageInput />
      </Window>
      <Thread />
    </Channel>
  </WithComponents>
);

Quick Actions

Quick actions are regular React components with no menu props. They usually read all needed state from context hooks.

import {
  Channel,
  MessageActions,
  WithComponents,
  defaultMessageActionSet,
  useMessageContext,
} from "stream-chat-react";

const CopyIdAction = () => {
  const { message } = useMessageContext();

  return (
    <button onClick={() => navigator.clipboard.writeText(message.id)}>
      Copy ID
    </button>
  );
};

const CustomMessageActions = () => (
  <MessageActions
    messageActionSet={[
      ...defaultMessageActionSet,
      {
        Component: CopyIdAction,
        placement: "quick",
        type: "copy-id",
      },
    ]}
  />
);

const App = () => (
  <WithComponents overrides={{ MessageActions: CustomMessageActions }}>
    <Channel>{/* ... */}</Channel>
  </WithComponents>
);

Filtering And Permissions

MessageActions applies the SDK base filter by default. That filter removes actions that do not make sense for the current message, user permissions, thread state, or channel capabilities.

Use the default filter unless you are intentionally taking over permission logic yourself.

import { MessageActions, defaultMessageActionSet } from "stream-chat-react";

const CustomMessageActions = () => {
  const messageActionSet = defaultMessageActionSet.filter(
    ({ type }) => type !== "markUnread",
  );

  return <MessageActions messageActionSet={messageActionSet} />;
};

If you pass disableBaseMessageActionSetFilter, your custom set is used as-is. Only do that when your custom action logic already enforces the same constraints the SDK would normally enforce.