# 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:

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

## Best Practices

- Start from `defaultMessageActionSet` unless you need a fully custom action surface.
- Preserve the default `quick-dropdown-toggle` item unless you are intentionally rebuilding the dropdown trigger too.
- 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 `quick`, `dropdown`, or `quick-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.

<admonition type="note">

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.

</admonition>

## Basic Customization

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

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

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

  return (
    <button
      {...props}
      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.

```tsx
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.

```tsx
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.


---

This page was last updated at 2026-07-09T16:01:15.900Z.

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react/experimental/message-actions/](https://getstream.io/chat/docs/sdk/react/experimental/message-actions/).