import {
type ContextMenuItemProps,
Channel,
MessageActions,
WithComponents,
defaultMessageActionSet,
useMessageContext,
} from "stream-chat-react";
const YellAction = ({ closeMenu }: ContextMenuItemProps) => {
const { message } = useMessageContext("YellAction");
return (
<button
className="str-chat__message-actions-list-item-button"
onClick={() => {
window.alert(`Yell action clicked on message: ${message.id}!`);
closeMenu();
}}
>
Yell
</button>
);
};
const CustomMessageActions = () => (
<MessageActions
messageActionSet={[
...defaultMessageActionSet,
{
Component: YellAction,
placement: "dropdown",
type: "yell",
},
]}
/>
);
export const WrappedChannel = ({ children }) => (
<WithComponents overrides={{ MessageActions: CustomMessageActions }}>
<Channel>{children}</Channel>
</WithComponents>
);Message Actions
This example shows how to add custom actions to the SDK message action menu.
Best Practices
- Start from
defaultMessageActionSetunless you need a fully custom action surface. - Preserve the default
quick-dropdown-toggleitem unless you are intentionally replacing the dropdown trigger. - Keep custom actions small and read message state from hooks like
useMessageContext(). - Register custom actions with
WithComponentsso the same override works inMessageList,Thread, andVirtualizedMessageList. - Keep action handlers fast and idempotent.
- Re-check custom actions in both channel and thread views because the SDK filters some actions differently across contexts.
Built-in Actions
The default action set can include these action types:
deleteeditflagmarkUnreadmutepinquotereactremindMereplyresendsaveForLater
markUnread is only available for foreign messages when the connected user also has the required read-events capability.
Add A Custom Dropdown Action
Use defaultMessageActionSet as a base and append your own dropdown item.
Because the dropdown trigger is now part of the action set, starting from defaultMessageActionSet preserves the current default menu button automatically.
Add A Quick Action
Quick actions render inline next to the message instead of inside the dropdown menu.
import {
Channel,
MessageActions,
WithComponents,
defaultMessageActionSet,
useMessageContext,
} from "stream-chat-react";
const CopyIdAction = () => {
const { message } = useMessageContext("CopyIdAction");
return (
<button onClick={() => navigator.clipboard.writeText(message.id)}>
Copy ID
</button>
);
};
const CustomMessageActions = () => (
<MessageActions
messageActionSet={[
...defaultMessageActionSet,
{
Component: CopyIdAction,
placement: "quick",
type: "copy-id",
},
]}
/>
);
export const WrappedChannel = ({ children }) => (
<WithComponents overrides={{ MessageActions: CustomMessageActions }}>
<Channel>{children}</Channel>
</WithComponents>
);Filter The Defaults
The SDK filters the default action set based on permissions, message state, and thread context. In most cases, keep that filtering enabled and only remove or add items intentionally.
import { MessageActions, defaultMessageActionSet } from "stream-chat-react";
const CustomMessageActions = () => (
<MessageActions
messageActionSet={defaultMessageActionSet.filter(
({ placement, type }) =>
placement === "quick-dropdown-toggle" || type !== "markUnread",
)}
/>
);If you pass disableBaseMessageActionSetFilter, the SDK stops applying that default permission/state filtering. Only do that if your custom actions already enforce the same constraints.
If you build a fully custom messageActionSet and still want dropdown actions, add a quick-dropdown-toggle item explicitly. The SDK no longer injects the menu trigger for you.