import { MessageActions } from "stream-chat-react";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:
Best Practices
- Start from
defaultMessageActionSetunless you need a fully custom action surface. - Preserve the default
quick-dropdown-toggleitem unless you are intentionally rebuilding the dropdown trigger too. - Keep custom action components small and read message state from hooks like
useMessageContext(). - Use
WithComponentsto scope action overrides to the part of the app that needs them. - Define a custom
messageActionSetexplicitly 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
placementofquick,dropdown, orquick-dropdown-toggle - a
Component
Quick actions render inline next to the message. Dropdown actions render inside the ContextMenu opened by the actions button.
The quick-dropdown-toggle item renders that menu trigger button. The SDK no longer injects it automatically when you provide a custom action set.
The exported defaultMessageActionSet already includes the default SDK actions and ordering. In most cases, start from that set and filter or extend it.
If you build a fully custom messageActionSet and still want dropdown actions, include a quick-dropdown-toggle item yourself. Without it, dropdown actions stay registered but no menu trigger is rendered.
Basic Customization
This example removes the default delete action and adds one custom dropdown action.
import {
type ContextMenuItemProps,
Channel,
ChannelHeader,
MessageActions,
MessageComposer,
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(
({ placement, type }) =>
placement === "quick-dropdown-toggle" || type !== "delete",
),
{
Component: BookmarkAction,
placement: "dropdown" as const,
type: "bookmark",
},
];
return <MessageActions messageActionSet={messageActionSet} />;
};
const App = () => (
<WithComponents overrides={{ MessageActions: CustomMessageActions }}>
<Channel>
<Window>
<ChannelHeader />
<MessageList />
<MessageComposer />
</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.
For example, markUnread is only available for foreign messages when the connected user also has the required read-events capability.
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.