# Flagging, muting, blocking and bans

> **Info:** This is release candidate documentation for **Stream Chat React SDK v15**. For the latest stable version, see the [latest version (v14)](https://getstream.io/chat/docs/sdk/react/).

Four tools keep conversations safe, and they differ in who uses them and whom they affect:

- **Flagging** reports a message or a user to the app's moderators.
- **Muting** silences a user, or a channel, for the person who mutes.
- **Blocking** cuts off a user's direct messages to the person who blocks.
- **Banning** stops a user from posting, in one channel or in the whole app. Moderators ban; a shadow ban hides the user's messages without telling them.

This page covers what users see of each one, what it calls in the state layer, and how to change the actions.

## What users get

The user opens the menu on a message from Bob. Besides the other message actions they see:

- **Flag**, which reports the message and confirms with "Message has been successfully flagged";
- **Mute**, which mutes Bob and confirms with "Bob has been muted". Once Bob is muted, the action reads **Unmute**;
- **Block User**, which blocks Bob. Once he's blocked, it reads **Unblock**.

The same tools appear in other places:

- **Channel list rows** have a mute button for the channel. In a direct message channel the row menu has **Block User**, which bans the other member from that channel. See [Channel lists](https://getstream.io/chat/docs/sdk/react/v15/features/channels/channel-lists/#the-row-actions).
- **Channel details** have a **Mute chat** switch. In a direct message channel they also have a switch to mute the other person and a **Block user** button. A member's profile has **Mute user** and **Block user**. See [Channel details and members](https://getstream.io/chat/docs/sdk/react/v15/features/channels/channel-details-and-members/).

Two things show in the message list itself. When moderation removes a message, its author sees "Message was blocked by moderation policies" in its place. A message marked as `shadowed`, the mark the server puts on a shadow-banned user's messages, shows the same text. A message that moderation bounces back to its author offers to edit it, delete it or send it anyway; see [Sending, editing and deleting](https://getstream.io/chat/docs/sdk/react/v15/features/messaging/sending-editing-and-deleting/).

## How it works across the layers

### State: what each tool calls

Every tool is a call to the server. None of them changes local state before the server answers: the state that follows each one changes with the response or with the server's event.

| Tool                 | What it calls                                                                                  | What changes on the client                                                                      |
| -------------------- | ---------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| Flag a message       | `client.moderation.flagMessage({ message_id, reason?, ...options })`                           | Nothing. The flag goes to the moderation queue.                                                 |
| Flag a user          | `client.moderation.flagUser({ user_id, reason?, ...options })`                                 | Nothing                                                                                         |
| Mute a user          | `client.moderation.mute({ target_ids, timeout? })`, `client.moderation.unmute({ target_ids })` | `client.mutedUsersStore`, when `notification.mutes_updated` arrives                             |
| Mute a channel       | `Channel.mute()`, `Channel.unmute()`                                                           | `channel.state.muteStatus`, when `notification.channel_mutes_updated` arrives                   |
| Block a user         | `client.blockUser(userId)`, `client.unblockUser(userId)`                                       | `client.blockedUsers`, when the response arrives                                                |
| Ban in a channel     | `Channel.banUser(userId, options)`, `Channel.unbanUser(userId)`                                | The member's `banned` or `shadow_banned` in `channel.state.members`, when `user.banned` arrives |
| Ban in the whole app | `client.moderation.ban({ target_user_id, ... })`, `client.unbanUser(userId)`                   | No client state of its own                                                                      |

The sections below explain each tool.

#### Flagging

A flag sends a message or a user to the moderation queue, where moderators review it in the [Stream Dashboard](https://getstream.io/signin/?product=chat). The reporting user's view doesn't change. Pass a reason to help moderators sort the queue:

```ts
await client.moderation.flagMessage({ message_id: message.id, reason: "spam" });
await client.moderation.flagUser({ user_id: "bob", reason: "harassment" });
```

The Flag action calls `client.moderation.flagMessage()` with no reason. Nothing in the SDK flags a user; call `client.moderation.flagUser()` from your own UI.

#### Muting users

A muted user's messages still arrive and still show in the message list: the SDK doesn't hide them. They don't count as unread, and the server sends no push notifications for them. `timeout` mutes for that many minutes:

```ts
// Mute Bob for one day.
await client.moderation.mute({ target_ids: ["bob"], timeout: 24 * 60 });

await client.moderation.unmute({ target_ids: ["bob"] });
```

The user's mute list is `client.mutedUsersStore`, a store whose `mutedUsers` holds one entry per muted user, with the user as `target`. `client.userMuteStatus(userId)` reads it once. To follow it in a component, subscribe to the store:

```tsx
import type { UserMuteResponse } from "stream-chat";
import { useChatContext, useStateStore } from "stream-chat-react";

const mutedUsersSelector = ({
  mutedUsers,
}: {
  mutedUsers: UserMuteResponse[];
}) => ({ mutedUsers });

export const MutedUsersList = () => {
  const { client } = useChatContext();
  const { mutedUsers } = useStateStore(
    client.mutedUsersStore,
    mutedUsersSelector,
  );

  return (
    <ul>
      {mutedUsers.map(({ target }) => (
        <li key={target?.id}>
          {target?.name ?? target?.id}{" "}
          <button
            onClick={() =>
              target && client.moderation.unmute({ target_ids: [target.id] })
            }
            type="button"
          >
            Unmute
          </button>
        </li>
      ))}
    </ul>
  );
};
```

#### Muting channels

A muted channel's new messages don't count as unread, and its row in the channel list shows a mute icon and no unread badge. `Channel.mute({ expiration })` mutes for that many milliseconds. [What channels and threads hold](https://getstream.io/chat/docs/sdk/react/v15/state/channels-and-threads/what-channels-and-threads-hold/#mute) explains `muteStatus`.

#### Blocking

Blocking is personal: it changes what the blocking user gets, not what anyone else sees. The server hides the direct message channels between the two users from the one who blocked, and stops events and push notifications from the blocked user in them. Group channels aren't affected, and the blocked user's messages there still show. [Block](https://getstream.io/chat/docs/javascript/moderation/#block) in the Chat API docs lists every effect.

```ts
await client.blockUser("bob");
await client.unblockUser("bob");

// The blocked users, with each user's details.
const { blocks } = await client.getBlockedUsers();
console.log(blocks.map(({ blocked_user }) => blocked_user.name));
```

The ids of the blocked users are in the `client.blockedUsers` store, as `userIds`. The client fills it when it connects, and `client.blockUser()`, `client.unblockUser()` and `client.getBlockedUsers()` update it after the server answers.

#### Bans and shadow bans

A ban stops a user from posting. `Channel.banUser(userId, options)` bans them from one channel, and needs the `ban-channel-members` capability. Its options are optional fields, so pass `{}` for none:

- `reason`, an explanation for the ban;
- `timeout`, the ban's length in minutes;
- `shadow: true`, for a shadow ban.

```ts
// Ban Bob from this channel for an hour.
await channel.banUser("bob", { reason: "Spam", timeout: 60 });

// Shadow ban: Bob can still post, and isn't told about the ban.
await channel.banUser("bob", { shadow: true });

await channel.unbanUser("bob");
```

`client.moderation.ban({ target_user_id })` bans a user from the whole app, and `client.unbanUser(userId)` lifts the ban. Apps usually ban app-wide from the server. [Ban](https://getstream.io/moderation/docs/node/content-moderation/flag-mute-ban/#ban) in the Moderation docs covers bans, shadow bans and their options.

The SDK doesn't check bans itself: the server rejects a banned user's messages, and the message list marks them as failed. When the client receives a message marked `shadowed`, it renders the "blocked by moderation" text in its place, and the message doesn't count as unread.

### UI: the actions and the blocked message

The message actions come from `defaultMessageActionSet`, which `MessageActions` renders. Each action reads the message from context and calls the state layer:

| Action              | `type`      | What it calls                                                             | Shown when                                     |
| ------------------- | ----------- | ------------------------------------------------------------------------- | ---------------------------------------------- |
| Flag                | `flag`      | `client.moderation.flagMessage({ message_id: message.id })`               | Not the user's own message, and `flag-message` |
| Mute, Unmute        | `mute`      | `client.moderation.mute()` or `client.moderation.unmute()` for the author | Not the user's own message, and `mute-channel` |
| Block User, Unblock | `blockUser` | `client.blockUser()` or `client.unblockUser()` for the author             | Not the user's own message                     |

Flag and Mute report their result through `client.notifications`; Block User reports nothing. [Message actions](https://getstream.io/chat/docs/sdk/react/v15/features/messaging/message-actions/) lists every message action.

The channel list rows and channel details have their own actions. They call the same methods, except the channel list's Block User, which calls `Channel.banUser()`. Their tables are on [Channel lists](https://getstream.io/chat/docs/sdk/react/v15/features/channels/channel-lists/#the-row-actions) and [Channel details and members](https://getstream.io/chat/docs/sdk/react/v15/features/channels/channel-details-and-members/#turning-it-on-or-off).

`MessageUI` renders `MessageBlocked` instead of the message when the message is marked `shadowed`, or when it's an `error` message that moderation removed. The default shows "Message was blocked by moderation policies".

Channel types can also offer the `/ban`, `/unban`, `/mute` and `/unmute` commands in the composer; see [Commands and Giphy](https://getstream.io/chat/docs/sdk/react/v15/features/rich-content/commands-and-giphy/).

## Turning it on or off

These tools are always available. Who sees each action depends on the user's capabilities in the channel, which the server derives from their roles and the channel type's permissions:

- **Flag** needs `flag-message`.
- **Mute** needs `mute-channel` in the message menu, and for channels. Muting a user from channel details checks no capability.
- **Ban** needs `ban-channel-members`. In channel details, Block user in a direct message channel needs it too.
- **Block User** in the message menu and in a member's profile needs no capability, because blocking is personal.

The channel type's settings and the moderation policies on the server decide the rest, such as which messages moderation removes. See [Moderation for Chat](https://getstream.io/chat/docs/javascript/moderation/) and [Permissions reference](https://getstream.io/chat/docs/javascript/permissions-reference/).

To remove an action from the message menu, filter it out of the action set:

```tsx
import type { PropsWithChildren } from "react";
import {
  MessageActions,
  WithComponents,
  defaultMessageActionSet,
} from "stream-chat-react";

// Keeps the "more" button, which has no type, and drops Block User.
const messageActionSet = defaultMessageActionSet.filter(
  (action) =>
    action.placement === "quick-dropdown-toggle" || action.type !== "blockUser",
);

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

const overrides = { MessageActions: CustomMessageActions };

export const WithoutBlockAction = ({ children }: PropsWithChildren) => (
  <WithComponents overrides={overrides}>{children}</WithComponents>
);
```

## Customizing

### Flagging with a reason

This menu replaces Flag with **Report spam**, which flags the message with a reason. The action keeps the `flag` type, so the base filter still shows it only to users with `flag-message`:

```tsx
import type { PropsWithChildren } from "react";
import {
  ContextMenuButton,
  MessageActions,
  WithComponents,
  defaultMessageActionSet,
  useChatContext,
  useContextMenuContext,
  useMessageContext,
} from "stream-chat-react";
import type { MessageActionSetItem } from "stream-chat-react";

const ReportSpam = () => {
  const { client } = useChatContext();
  const { message } = useMessageContext();
  const { closeMenu } = useContextMenuContext();

  const report = async () => {
    closeMenu();
    try {
      await client.moderation.flagMessage({
        message_id: message.id,
        reason: "spam",
      });
      client.notifications.addSuccess({
        message: "Reported as spam",
        origin: { emitter: "ReportSpam" },
      });
    } catch {
      client.notifications.addError({
        message: "Couldn't report the message",
        origin: { emitter: "ReportSpam" },
      });
    }
  };

  return (
    <ContextMenuButton onClick={() => void report()}>
      Report spam
    </ContextMenuButton>
  );
};

const messageActionSet: MessageActionSetItem[] = defaultMessageActionSet.map(
  (item) =>
    item.placement === "dropdown" && item.type === "flag"
      ? { ...item, Component: ReportSpam }
      : item,
);

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

const overrides = { MessageActions: CustomMessageActions };

export const WithReportSpam = ({ children }: PropsWithChildren) => (
  <WithComponents overrides={overrides}>{children}</WithComponents>
);
```

[Actions](https://getstream.io/chat/docs/sdk/react/v15/ui/customizing/actions/) explains action sets, including the ones in the channel list and in channel details.

### The blocked message

Replace `MessageBlocked` through `WithComponents`. It renders inside the message, so it reads the message with `useMessageContext()`:

```tsx
import type { PropsWithChildren } from "react";
import { WithComponents, useMessageContext } from "stream-chat-react";

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

  return (
    <div className="str-chat__message str-chat__message--blocked">
      {message.shadowed
        ? "This message is hidden."
        : "This message was removed."}
    </div>
  );
};

const overrides = { MessageBlocked: HiddenMessage };

export const WithHiddenMessages = ({ children }: PropsWithChildren) => (
  <WithComponents overrides={overrides}>{children}</WithComponents>
);
```

### Showing who is blocked

`client.blockedUsers` is a store, so a component that subscribes to it updates when the user blocks or unblocks someone:

```tsx
import type { BlockedUsersState } from "stream-chat";
import { useChatContext, useStateStore } from "stream-chat-react";

const blockedSelector = ({ userIds }: BlockedUsersState) => ({ userIds });

export const BlockedBadge = ({ userId }: { userId: string }) => {
  const { client } = useChatContext();
  const { userIds } = useStateStore(client.blockedUsers, blockedSelector);

  return userIds.includes(userId) ? (
    <span className="badge">Blocked</span>
  ) : null;
};
```

## Reference

- [Message UI components](https://getstream.io/chat/docs/sdk/react/v15/component-reference/message/message-ui-components/): `MessageActions` and `MessageBlocked`.
- [ComponentContext](https://getstream.io/chat/docs/sdk/react/v15/component-reference/channel/componentcontext/): the `MessageActions` and `MessageBlocked` override keys.
- [Message hooks](https://getstream.io/chat/docs/sdk/react/v15/reference/hooks/message-hooks/): `useFlagHandler`, `useMuteHandler` and `useUserRole`.
- [Flag, mute and ban](https://getstream.io/moderation/docs/node/content-moderation/flag-mute-ban/) in the Moderation docs: the server side of each tool.
- [Moderation for Chat](https://getstream.io/chat/docs/javascript/moderation/) in the Chat API docs: blocking and how it affects chat.

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react/v15/features/moderation-and-safety/flagging-muting-blocking-and-bans/](https://getstream.io/chat/docs/sdk/react/v15/features/moderation-and-safety/flagging-muting-blocking-and-bans/).