# Actions

Every menu of actions in the SDK is built from an action set: a list of items, each naming an action and the component that renders it. Use this page to know which action sets there are, how the SDK filters them for each user, and how to add, remove or replace actions.

| Action set                                              | Where it shows                                                                                                                                       | Prop that takes your set                                                                                                                                             |
| ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Message actions](#message-actions)                     | Next to each message, and in its menu ([Message UI](https://getstream.io/chat/docs/sdk/react/ui/components/message-ui/#parts-of-the-default-message-ui))                 | `messageActionSet` on `<MessageActions/>`                                                                                                                            |
| [Channel list item actions](#channel-list-item-actions) | On each channel list item ([Channel list UI](https://getstream.io/chat/docs/sdk/react/ui/components/channel-list-ui/#channel-preview-actions))                           | None. Register your own component under the `ChannelListItemActionButtons` key with [`<WithComponents/>`](https://getstream.io/chat/docs/sdk/react/ui/components/overriding-components/) |
| [Attachment selector](#attachment-selector-actions)     | In the menu beside the composer's input ([Message composer UI](https://getstream.io/chat/docs/sdk/react/ui/components/message-composer-ui/#actions))                     | `attachmentSelectorActionSet` on `<AttachmentSelector/>`                                                                                                             |
| [Channel management](#channel-management)               | At the bottom of the Channel info section of channel details ([Channel details UI](https://getstream.io/chat/docs/sdk/react/ui/components/channel-details-ui/#sections)) | `channelManagementActionSet` on `<ChannelManagementView/>`                                                                                                           |
| [Channel member](#channel-member)                       | In a member's detail view, opened from the Members section of channel details                                                                        | `channelMemberActionSet` on `<ChannelMemberDetail/>`                                                                                                                 |
| [Channel members header](#channel-members-header)       | In the header of the Members section of channel details                                                                                              | `headerActionSet` on `<ChannelMembersView/>`                                                                                                                         |

Each set has a default you can start from, and a base filter that removes the actions the user can't take. Each section names them.

The last three are channel details actions, exported from `stream-chat-react/channel-detail`. Everything else is exported from `stream-chat-react`.

## How an action set works

An action set is a list of items. Every item has:

- a `type`, the action's name, which the base filter uses to recognize it. The menu button item, described in [Placements](#placements), has none;
- a component that renders the action;
- in the message, channel list item and channel members header sets, a `placement` that decides where the action renders.

### Placements

| Placement               | In the sets                                        | Where the item renders                                                                                                                                                                                                                                                                  |
| ----------------------- | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `quick`                 | Message, channel list item, channel members header | As a button: next to the message, on the channel list item, or in the members header                                                                                                                                                                                                    |
| `dropdown`              | Message, channel list item, channel members header | In the menu                                                                                                                                                                                                                                                                             |
| `quick-dropdown-toggle` | Message, channel list item, channel members header | As the menu button that opens the `dropdown` items: "…" on messages and channel list items, "Actions" in the members header. It's an item of its own, with no `type`, and it renders only while at least one `dropdown` item is left. Without it, the `dropdown` items can't be opened. |

### What happens when a menu renders

The same three steps apply to every set:

1. **The base filter drops the actions the user can't take.** It reads the user's capabilities, the channel's settings, and what the action applies to: the message, the channel or the member. A `type` the filter doesn't know passes it, so your own actions always show, and checking whether the user may use them is up to your component.
2. **The placement, in the sets that have one, decides where an action goes**, as in [Placements](#placements).
3. **The list's order is the order in the menu.**

When the filter leaves no action, the message actions, the attachment selector, the channel list item actions and the channel members header actions render nothing.

## Message actions

![Bob's message hovered: the quick actions next to it, react, reply and the menu button, and the open menu with Thread Reply, Quote Reply, Pin, Copy Message, Mark as unread, Flag, Mute and Block User](https://getstream.io/docs-assets/images/c981619cf1b1.png)

The menu of someone else's message. Edit and Delete show on the user's own messages, and Remind me and Save for later when reminders are on.

`<MessageActions/>` renders one message's actions from a message action set. By default, that's `defaultMessageActionSet`.

### The message action set

A message action set is an array, and each item in it describes one action:

| Field       | What it is                                                                                                                                                                                                                                                                 |
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`      | The action's name, such as `"edit"`. The base filter checks the default names, as in [Checks per action](#checks-per-action), and lets any other name through. Code that changes a set finds an item by it. The menu button item has none.                                 |
| `placement` | Where the action renders: `quick` next to the message, `dropdown` in the menu, or `quick-dropdown-toggle` as the menu button.                                                                                                                                              |
| `Component` | The component that renders the action. It reads the message with `useMessageContext()`. A `dropdown` component gets the props of a menu item, `ContextMenuItemProps`, and the `quick-dropdown-toggle` component gets the props of a button. A `quick` component gets none. |
| `isVisible` | Optional. A rule that gets the message, whether it's the user's own and whether it's a thread reply, and returns whether the action shows. It can only hide an action, as [Rules on the items](#rules-on-the-items) explains. The menu button item has none.               |

For each message, `<MessageActions/>` drops the items the base filter or their own `isVisible` rules out, then renders the rest by placement, in the set's order.

>
> **Info:** **Example**
>
> The `edit` item of the default set has the type `"edit"`, the placement `dropdown`, an Edit button as its `Component`, and an `isVisible` that hides it on a message with a poll or a giphy:
>
> 1. The user opens the menu on someone else's message. The base filter drops `edit`, because the user may not edit it, and the menu has no Edit.
> 2. The user opens the menu on their own text message. The base filter keeps `edit`, `isVisible` returns `true`, and Edit shows in the menu.
> 3. The user opens the menu on their own poll message. The base filter keeps `edit`, but `isVisible` returns `false`, and the menu has no Edit.
>

The default set starts like this:

```ts
[
  { Component: QuickDropdownToggle, placement: "quick-dropdown-toggle" },
  { Component: QuickReply, placement: "quick", type: "reply" },
  { Component: QuickReact, placement: "quick", type: "react" },
  { Component: AddReaction, placement: "dropdown", type: "react" },
  { Component: ThreadReply, placement: "dropdown", type: "reply" },
  // quote, download, pin, copy, resend, edit, mark unread, remind me,
  // save for later, flag, mute, delete, block user
];
```

An item's placement decides where it renders, and what its component must do:

| Placement               | Where it renders                                                                  | Your component                                                                             |
| ----------------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| `dropdown`              | In the menu                                                                       | Closes the menu when the action is done: call `closeMenu()` from `useContextMenuContext()` |
| `quick`                 | Next to the message                                                               | Needs nothing extra: there's no menu to close                                              |
| `quick-dropdown-toggle` | As the menu button that opens the menu, when at least one `dropdown` item is left | Keep one in your set, or the `dropdown` items can't be opened                              |

Next to the message, the menu button always comes first, then the `quick` items in list order. The SDK's stylesheet lays them out from right to left.

To use your own set, register a component under the `MessageActions` key that renders `<MessageActions messageActionSet={yourSet} />`. Here is a `ChannelWithCustomMessageActions` component, a channel whose messages have no "Block user" action and a "Copy link" one:

```tsx
import {
  Channel,
  ContextMenuButton,
  defaultMessageActionSet,
  MessageActions,
  MessageComposer,
  MessageList,
  useComponentContextIcons,
  useContextMenuContext,
  useMessageContext,
  WithComponents,
} from "stream-chat-react";
import type { MessageActionSetItem } from "stream-chat-react";
import type { Channel as StreamChannel } from "stream-chat";

const CopyLinkAction = () => {
  const { IconLink } = useComponentContextIcons();
  const { closeMenu } = useContextMenuContext();
  const { message } = useMessageContext();

  return (
    <ContextMenuButton
      Icon={IconLink}
      onClick={() => {
        void navigator.clipboard.writeText(
          `${window.location.origin}/messages/${message.id}`,
        );
        closeMenu();
      }}
    >
      Copy link
    </ContextMenuButton>
  );
};

// Declared once: the base filter recomputes whenever it gets a new list.
const messageActionSet: MessageActionSetItem[] = [
  ...defaultMessageActionSet.filter(
    (item) =>
      item.placement === "quick-dropdown-toggle" || item.type !== "blockUser",
  ),
  { Component: CopyLinkAction, placement: "dropdown", type: "copyLink" },
];

const CustomMessageActions = () => (
  <MessageActions messageActionSet={messageActionSet} />
);

const messageOverrides = { MessageActions: CustomMessageActions };

// The messages in this channel get the custom actions.
export const ChannelWithCustomMessageActions = ({
  channel,
}: {
  channel: StreamChannel;
}) => (
  <WithComponents overrides={messageOverrides}>
    <Channel channel={channel}>
      <MessageList />
      <MessageComposer />
    </Channel>
  </WithComponents>
);
```

The base filter gives some messages no actions, or only a few, such as a message still sending or one that failed to send; [Message actions](https://getstream.io/chat/docs/sdk/react/features/messaging/message-actions/#how-it-works-across-the-layers) lists them. On a reply in a thread it drops `pin`, `reply` and `markUnread`. On any other message it keeps each action that passes the base filter's check in [Checks per action](#checks-per-action), and never `resendMessage`.

### Checks per action

| Action                       | Shown when                                                                                                    | Checked by             |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------- | ---------------------- |
| `edit`                       | The user may edit the message                                                                                 | base filter            |
| `edit`                       | The message has no poll and isn't a giphy                                                                     | the item's `isVisible` |
| `delete`                     | The user may delete the message, and it isn't deleted already                                                 | base filter            |
| `flag`, `markUnread`, `mute` | The message isn't the user's own, and the user has `flag-message`, `read-events` or `mute-channel` capability | base filter            |
| `blockUser`                  | The message isn't the user's own                                                                              | base filter            |
| `quote`, `react`, `reply`    | The user has the capability to quote, react or reply in the channel                                           | base filter            |
| `remindMe`, `saveForLater`   | Message reminders are on for the channel: `channel.configState`'s `userMessageReminders.enabled`              | base filter            |
| `copyMessageText`            | The message has text                                                                                          | the item's `isVisible` |
| `download`                   | The message has a file, image, video, audio or voice recording attachment                                     | the item's `isVisible` |
| `pin`                        | The user has the `pin-message` capability                                                                     | base filter            |

### Rules on the items

An item can carry its own rule as `isVisible(params)`. It gets `MessageActionVisibilityFilterParams`: `isMyMessage`, `isThreadReply` and the `message`. The action shows only while `isVisible` returns `true`.

An item's rule can only hide an action: one the base filter drops stays hidden. The rule also applies to a message that failed to send, and with `disableBaseMessageActionSetFilter`.

An item without `isVisible` shows wherever the base filter allows it. So a set that writes its own `edit`, `copyMessageText` or `download` item by hand loses the default rule: without it, `edit` shows on a poll message the user may update. Spread the default item, or copy its `isVisible`. To change a rule, replace the item's `isVisible`:

```ts
import { defaultMessageActionSet } from "stream-chat-react";
import type { MessageActionSetItem } from "stream-chat-react";

// Lets users edit the text and attachments sent with a poll; a giphy still can't be edited.
const messageActionSet: MessageActionSetItem[] = defaultMessageActionSet.map(
  (item) =>
    item.placement !== "quick-dropdown-toggle" && item.type === "edit"
      ? { ...item, isVisible: ({ message }) => message.command !== "giphy" }
      : item,
);
```

`<MessageActions/>` takes `disableBaseMessageActionSetFilter` to skip the base filter's checks. Pass it only when your components check the base filter's conditions in [Checks per action](#checks-per-action) themselves. To build your own actions component, run `useBaseMessageActionSetFilter(set)` on your list, as `<MessageActions/>` does.

What each default action does, and the actions that come with flagging, muting and reminders, are on [Message actions](https://getstream.io/chat/docs/sdk/react/features/messaging/message-actions/).

## Channel list item actions

![The Bob channel list item hovered: the quick actions, the menu button and the mute button, and the open menu with Archive, Pin and Leave Channel](https://getstream.io/docs-assets/images/8f0a6b5a8370.png)

`<ChannelListItemActionButtons/>` renders each channel list item's actions. To customize them, replace it with your own component, as in [Replacing the actions](#replacing-the-actions). It renders `defaultChannelActionSet`, filtered by `useBaseChannelActionSetFilter`.

The default set, in this order:

| Type                  | Placement               | Shown when                                                                                                                         |
| --------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| None: the menu button | `quick-dropdown-toggle` | At least one `dropdown` action is left                                                                                             |
| `mute`                | `quick`                 | The user has the `mute-channel` capability                                                                                         |
| `archive`             | `dropdown`              | The user is a member of the channel                                                                                                |
| `ban` ("Block User")  | `dropdown`              | The channel is a direct message (the user is a member, and it has 2 members) and the user has the `ban-channel-members` capability |
| `pin`                 | `dropdown`              | The user is a member of the channel                                                                                                |
| `leave`               | `dropdown`              | The user has the `leave-channel` capability                                                                                        |

The placements work as in [Message actions](#message-actions): `quick` on the channel list item, `dropdown` in the menu.

### Replacing the actions

The set has no prop, and neither it nor its filter is exported, so your component starts from scratch:

1. Write a component that renders the actions you want. It renders inside the channel list item, so `useChannelListItemContext()` gives it the item's channel.
2. Register it under the `ChannelListItemActionButtons` key with `<WithComponents/>`, around the channel list.

Here is a `ChannelListWithMarkAsRead` component, a channel list whose items show a "Mark as read" button, `MarkAsReadAction`, instead of their actions:

```tsx
import type { ChannelPaginator, OwnCapabilitiesState } from "stream-chat";
import {
  Button,
  ChannelList,
  useChannelListItemContext,
  useComponentContextIcons,
  useStateStore,
  WithComponents,
} from "stream-chat-react";

const capabilitiesSelector = ({ ownCapabilities }: OwnCapabilitiesState) => ({
  ownCapabilities,
});

const MarkAsReadAction = () => {
  const { channel } = useChannelListItemContext();
  const { IconChecks } = useComponentContextIcons();
  const { ownCapabilities } = useStateStore(
    channel.state,
    capabilitiesSelector,
  );

  if (!ownCapabilities.includes("read-events")) return null;

  return (
    <div className="str-chat__channel-list-item__action-buttons">
      <Button
        appearance="ghost"
        aria-label="Mark as read"
        circular
        onClick={() => void channel.markRead()}
        size="sm"
        variant="secondary"
      >
        <IconChecks />
      </Button>
    </div>
  );
};

const channelListItemOverrides = {
  ChannelListItemActionButtons: MarkAsReadAction,
};

// Every item of this channel list gets the "Mark as read" button instead of its actions.
export const ChannelListWithMarkAsRead = ({
  paginator,
}: {
  paginator: ChannelPaginator;
}) => (
  <WithComponents overrides={channelListItemOverrides}>
    <ChannelList paginator={paginator} />
  </WithComponents>
);
```

Your component replaces the default buttons, menu included, and does its own capability checks, here through `channel.state`. The actions themselves are on [Channel lists](https://getstream.io/chat/docs/sdk/react/features/channels/channel-lists/).

## Attachment selector actions

![The composer with the attachment selector menu open: File, Location and Commands](https://getstream.io/docs-assets/images/8179944cd10b.png)

The menu in a channel with polls disabled. [Attachment Selector](https://getstream.io/chat/docs/sdk/react/component-reference/message-composer/attachment-selector/#default-behavior) lists when Poll and the other actions show.

`<AttachmentSelector/>`, the button beside the composer's input, renders a menu of `defaultAttachmentSelectorActionSet`, filtered by a filter built into it. The filter isn't exported, and it always runs. An item of this set has a `type` and an `ActionButton`, its entry in the menu, but no placement.

To change the set, see [Custom action sets](https://getstream.io/chat/docs/sdk/react/component-reference/message-composer/attachment-selector/#custom-action-sets), which lists the fields of an item. To give an action of your own a button that replaces the menu, see [Standalone buttons](https://getstream.io/chat/docs/sdk/react/component-reference/message-composer/attachment-selector/#standalone-buttons).

## Channel details actions

Channel details come from `stream-chat-react/channel-detail`. An app usually opens them from the channel header, by passing `AvatarWithChannelDetail` as its `Avatar`. They have three action sets, each rendered by one view. [Using your own sets](#using-your-own-sets) changes two of them in one example.

### Channel management

![Channel details, Group info: the channel management actions Mute Chat, Leave Chat and Delete Chat at the bottom](https://getstream.io/docs-assets/images/058e641037a9.png)

The actions of a group channel.

`<ChannelManagementView/>`, the Channel info section, renders them at its bottom: `defaultChannelManagementActionSet`, filtered by `useBaseChannelManagementActionSetFilter`. The filter always runs.

#### The channel management action set

An item has no placement, and these fields:

| Field       | What it is                                                                                                           |
| ----------- | -------------------------------------------------------------------------------------------------------------------- |
| `type`      | The action's name, such as `"deleteChat"`. The base filter checks the default names and lets any other name through. |
| `Component` | The component that renders the action. It gets no props: it reads the channel with `useChannelDetailContext()`.      |

The view renders the items the base filter keeps, in the set's order.

The default set, in this order:

| Type           | Shown when                                                                                               |
| -------------- | -------------------------------------------------------------------------------------------------------- |
| `muteChannel`  | The user has the `mute-channel` capability                                                               |
| `muteUser`     | The channel is a direct message with another user                                                        |
| `blockUser`    | The channel is a direct message with another user, and the user has the `ban-channel-members` capability |
| `leaveChannel` | The channel is a group channel, and the user has the `leave-channel` capability                          |
| `deleteChat`   | The user has the `delete-channel` capability                                                             |

### Channel member

![Channel details, Member detail for Bob: the channel member actions Send Direct Message, Mute User, Block User and Remove User](https://getstream.io/docs-assets/images/800ed3c9e333.png)

`<ChannelMemberDetail/>`, a member's detail view opened from the Members section, renders the member's actions: `defaultChannelMemberActionSet`, filtered by `useBaseChannelMemberActionSetFilter`. The filter always runs.

#### The channel member action set

An item has these fields:

| Field       | What it is                                                                                                                                                                                                                           |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `type`      | The action's name, such as `"removeUser"`. The base filter checks the default names and lets any other name through.                                                                                                                 |
| `Component` | The component that renders the action. It gets no props. It reads the member with `useChannelMemberActionContext()`, which gives `member`, `memberDisplayName` and `targetUserId`, and the channel with `useChannelDetailContext()`. |

The view renders the items the base filter keeps, in the set's order.

The default set, in this order:

| Type                                   | Shown when                                                                          |
| -------------------------------------- | ----------------------------------------------------------------------------------- |
| `sendMessage`, `muteUser`, `blockUser` | The member isn't the user                                                           |
| `removeUser`                           | The member isn't the user, and the user has the `update-channel-members` capability |

`<ChannelMembersView/>` opens this view from the members list without passing a set, so the Members section always shows the default member actions. Only a `<ChannelMemberDetail/>` you render yourself uses your set.

### Channel members header

![Channel details, the Members section: the header with the Add button, the channel members header action](https://getstream.io/docs-assets/images/98ea4d4a7417.png)

`<ChannelMembersView/>`, the Members section, renders these actions in its header, in browse mode: `defaultChannelMembersHeaderActionSet`, filtered by `useBaseChannelMembersHeaderActionSetFilter`.

#### The channel members header action set

An item has the same fields as a message action item:

| Field       | What it is                                                                                                                                                                                                                                                                                                                                                                             |
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`      | The action's name, such as `"addMembers"`. The base filter checks only `addMembers`. The menu button item has none.                                                                                                                                                                                                                                                                    |
| `placement` | Where the action renders: `quick` as a button in the header, `dropdown` in the header's menu, or `quick-dropdown-toggle` as the menu button.                                                                                                                                                                                                                                           |
| `Component` | The component that renders the action. A `quick` or `dropdown` component gets `modeController`, which switches the Members section between its modes, such as `browse` and `add`. [ChannelMembersView](https://getstream.io/chat/docs/sdk/react/component-reference/channel-detail/channel-detail-views/#channelmembersview) explains the modes. The `quick-dropdown-toggle` component is the menu button. |
| `isVisible` | Optional. A rule that gets `{ channel }` and returns whether the action shows. Use it for your own actions' permission checks. It can only hide an action. The menu button item has none.                                                                                                                                                                                              |

The default header actions drop the items that the base filter or their own `isVisible` rules out, then render the rest by placement, in the set's order.

The default set:

| Type         | Placement               | Shown when                                           |
| ------------ | ----------------------- | ---------------------------------------------------- |
| (none)       | `quick-dropdown-toggle` | At least one `dropdown` item is left                 |
| `addMembers` | `quick`                 | The user has the `update-channel-members` capability |

[Placements](#placements) says where each placement renders. Two of them ask more of your component here:

- A `dropdown` component closes the menu when its action is done: it calls `closeMenu()` from `useContextMenuContext()`.
- The `quick-dropdown-toggle` component is a `forwardRef` button. It gets a `ref`, `aria-expanded` and `onClick`, and spreads them onto its button. One that ignores them never opens the menu.

The default set already holds a menu button. Keep it when you build your set from the default one, and add one when you write a set from scratch, or the `dropdown` items can't be opened.

The filter runs in the default header actions only. `<ChannelMembersView/>` takes a `HeaderActions` component that replaces them, and it receives your `headerActionSet` unfiltered. To match the default, run `useBaseChannelMembersHeaderActionSetFilter(set)` in your component and render the `dropdown` items in a `<ContextMenu/>` anchored to the menu button, since `useContextMenuContext()` throws outside one. `DefaultHeaderActions` in `ChannelMembersHeaderActions.defaults.tsx` of the SDK shows the wiring.

### Using your own sets

Each view takes its set as a prop, and you reach the view through the sections of `<ChannelDetail/>`. Using your own sets takes six steps:

1. Write the components of your own actions.
2. Build your sets from the defaults.
3. Give each set to its view, and use that view as its section's content.
4. Give the sections to `<ChannelDetail/>`.
5. Open that `<ChannelDetail/>` from the channel header's avatar.
6. Render that header in a channel.

Here is a `ChannelWithCustomDetails` component that follows them. It removes "Delete chat", adds "Export chat" to the channel management actions, and adds "Copy invite link" to the members header. The comments mark the steps:

```tsx
import {
  Channel,
  ChannelHeader,
  ContextMenuButton,
  ListItemLayout,
  MessageComposer,
  MessageList,
  useContextMenuContext,
} from "stream-chat-react";
import type { Channel as StreamChannel } from "stream-chat";
import {
  AvatarWithChannelDetail,
  ChannelDetail,
  ChannelManagementView,
  ChannelMembersView,
  defaultChannelDetailSections,
  defaultChannelManagementActionSet,
  defaultChannelMembersHeaderActionSet,
  useChannelDetailContext,
} from "stream-chat-react/channel-detail";
import type {
  AvatarWithChannelDetailProps,
  ChannelDetailProps,
  ChannelManagementActionItem,
  ChannelMembersHeaderActionItem,
  SectionNavigatorSection,
  SectionNavigatorSectionContentProps,
} from "stream-chat-react/channel-detail";

// 1. Write the components of your own actions.
const ExportChatAction = () => {
  const { channel } = useChannelDetailContext();

  return (
    <ListItemLayout
      RootElement="button"
      rootProps={{
        onClick: () => window.open(`/exports/${channel.id}`),
        type: "button",
      }}
      title="Export chat"
    />
  );
};

const CopyInviteLinkAction = () => {
  const { channel } = useChannelDetailContext();
  const { closeMenu } = useContextMenuContext();

  return (
    <ContextMenuButton
      onClick={() => {
        void navigator.clipboard.writeText(
          `${window.location.origin}/join/${channel.id}`,
        );
        closeMenu();
      }}
    >
      Copy invite link
    </ContextMenuButton>
  );
};

// 2. Build your sets from the defaults.
const channelManagementActionSet: ChannelManagementActionItem[] = [
  ...defaultChannelManagementActionSet.filter(
    ({ type }) => type !== "deleteChat",
  ),
  { Component: ExportChatAction, type: "exportChat" },
];

const headerActionSet: ChannelMembersHeaderActionItem[] = [
  ...defaultChannelMembersHeaderActionSet,
  {
    Component: CopyInviteLinkAction,
    // An app's own action carries its own permission check.
    isVisible: ({ channel }) =>
      channel.data?.own_capabilities?.includes("update-channel-members") ??
      false,
    placement: "dropdown",
    type: "copyInviteLink",
  },
];

// 3. Give each set to its view, and use that view as its section's content.
const ManagementSection = (props: SectionNavigatorSectionContentProps) => (
  <ChannelManagementView
    {...props}
    channelManagementActionSet={channelManagementActionSet}
  />
);

const MembersSection = (props: SectionNavigatorSectionContentProps) => (
  <ChannelMembersView {...props} headerActionSet={headerActionSet} />
);

// 4. Give the sections to ChannelDetail.
const sections: SectionNavigatorSection[] = defaultChannelDetailSections.map(
  (section) => {
    if (section.id === "channel-info")
      return { ...section, SectionContent: ManagementSection };
    if (section.id === "channel-members")
      return { ...section, SectionContent: MembersSection };
    return section;
  },
);

const CustomChannelDetail = (props: ChannelDetailProps) => (
  <ChannelDetail {...props} sections={sections} />
);

// 5. Open that ChannelDetail from the channel header's avatar.
const HeaderAvatar = (props: AvatarWithChannelDetailProps) => (
  <AvatarWithChannelDetail {...props} ChannelDetail={CustomChannelDetail} />
);

const CustomChannelHeader = () => <ChannelHeader Avatar={HeaderAvatar} />;

// 6. Render the custom header in a channel.
export const ChannelWithCustomDetails = ({
  channel,
}: {
  channel: StreamChannel;
}) => (
  <Channel channel={channel}>
    <CustomChannelHeader />
    <MessageList />
    <MessageComposer />
  </Channel>
);
```

The views and their other props are on [Channel Detail Views](https://getstream.io/chat/docs/sdk/react/component-reference/channel-detail/channel-detail-views/), and the feature on [Channel details and members](https://getstream.io/chat/docs/sdk/react/features/channels/channel-details-and-members/).

## Adding, removing and replacing actions

Start from the default set and change it, rather than writing a list from scratch:

| To                          | Do                                                                                           |
| --------------------------- | -------------------------------------------------------------------------------------------- |
| Remove an action            | Filter out its `type`                                                                        |
| Add an action               | Put an item where you want it: the list's order is the order in the menu                     |
| Replace an action           | Keep its `type` and swap its component                                                       |
| Change when an action shows | Keep its `type` and replace its `isVisible`, as in [Rules on the items](#rules-on-the-items) |

Here is a `PinWithConfirmation` component that replaces the pin action, and a message action set that also adds `CopyLinkAction` from [Message actions](#message-actions) before "flag":

```tsx
// Asks before pinning or unpinning.
const PinWithConfirmation = () => {
  const { closeMenu } = useContextMenuContext();
  const { handlePin, message } = useMessageContext();

  return (
    <ContextMenuButton
      onClick={(event) => {
        if (
          window.confirm(
            message.pinned ? "Unpin this message?" : "Pin this message?",
          )
        ) {
          void handlePin(event);
        }
        closeMenu();
      }}
    >
      {message.pinned ? "Unpin" : "Pin"}
    </ContextMenuButton>
  );
};

// Replace the pin action's component, and add CopyLinkAction (from the Message actions example) before "flag".
const messageActionSet: MessageActionSetItem[] =
  defaultMessageActionSet.flatMap((item): MessageActionSetItem[] => {
    if (item.placement === "dropdown" && item.type === "pin") {
      return [{ ...item, Component: PinWithConfirmation }];
    }
    if (item.placement === "dropdown" && item.type === "flag") {
      return [
        { Component: CopyLinkAction, placement: "dropdown", type: "copyLink" },
        item,
      ];
    }
    return [item];
  });
```

A replaced action keeps its `type`, so the base filter still treats it as that action: `PinWithConfirmation` still shows only to users with the `pin-message` capability, and not on thread replies. An added action has a type the filter doesn't know: it always passes, so its component checks whether the user may use it and returns `null` when not. Keep the base filter on, so the default actions keep following the user's capabilities.

The [Custom message actions](https://getstream.io/chat/docs/sdk/react/ui-cookbooks/message-actions/custom-message-actions/) and [Product card](https://getstream.io/chat/docs/sdk/react/ui-cookbooks/custom-attachments/product-card/#adding-a-product-from-the-composer) recipes walk through complete examples.

## Used in

- [Message actions](https://getstream.io/chat/docs/sdk/react/features/messaging/message-actions/)
- [Flagging, muting, blocking and bans](https://getstream.io/chat/docs/sdk/react/features/moderation-and-safety/flagging-muting-blocking-and-bans/)
- [Channel lists](https://getstream.io/chat/docs/sdk/react/features/channels/channel-lists/)
- [Channel details and members](https://getstream.io/chat/docs/sdk/react/features/channels/channel-details-and-members/)

---

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