# Flagging, muting, blocking and bans

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

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 long-presses a message from Bob. Besides the other message actions, the menu has:

- **Flag Message**, which asks "Do you want to send a copy of this message to a moderator for further investigation?" and, once they confirm, says "Message has been successfully flagged";
- **Mute User**, which mutes Bob and says "Bob has been muted". Once Bob is muted, the action reads **Unmute User**;
- **Ban User**, when the user may ban members, which bans Bob. Once he's banned, it reads **Unban User**;
- **Block User**, which asks them to confirm and blocks Bob. Once he's blocked, it reads **Unblock User**.

The same tools appear in other places:

- **Channel list rows** have a swipe button that mutes the channel. In a direct message channel, the row's bottom sheet has **Mute User** and **Block User** for the other member. See [Channel lists](https://getstream.io/chat/docs/sdk/react-native/v10/features/channels/channel-lists/#the-row-actions).
- **Channel details** have a mute switch for the channel. In a direct message channel they also have a **Mute User** switch and **Block User**. The sheet that opens on a member has **Mute User** and **Block User**. See [Channel details and members](https://getstream.io/chat/docs/sdk/react-native/v10/features/channels/channel-details-and-members/).

Moderation also shows in the message list. When moderation removes a message, its author sees a small centered notice in its place, with the text the server sent, such as "Message was blocked by moderation policies". A message that moderation bounces back to its author offers to send it anyway, edit it or delete it; see [Bounced messages](https://getstream.io/chat/docs/sdk/react-native/v10/features/messaging/sending-editing-and-deleting/#bounced-messages). The messages of a shadow-banned user don't show for anyone else.

## 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" });
```

Flag Message 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, and the `useMutedUsers()` hook follows it in a component:

```tsx title="src/MutedUsersList.tsx"
import { Button, FlatList, StyleSheet, Text, View } from "react-native";
import { useChatContext, useMutedUsers } from "stream-chat-react-native";

export const MutedUsersList = () => {
  const { client } = useChatContext();
  const mutedUsers = useMutedUsers();

  return (
    <FlatList
      data={mutedUsers}
      keyExtractor={(mute) => mute.target?.id ?? ""}
      renderItem={({ item: { target } }) => (
        <View style={styles.row}>
          <Text>{target?.name ?? target?.id}</Text>
          <Button
            onPress={() => {
              if (target) {
                void client.moderation.unmute({ target_ids: [target.id] });
              }
            }}
            title="Unmute"
          />
        </View>
      )}
    />
  );
};

const styles = StyleSheet.create({
  row: {
    alignItems: "center",
    flexDirection: "row",
    justifyContent: "space-between",
    padding: 16,
  },
});
```

When the server's `notification.mutes_updated` arrives, the store changes and the list follows.

#### 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-native/v10/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 shows them as failed. Messages from a shadow-banned user reach other users marked `shadowed`. The client leaves them out of the message list and doesn't count them as unread.

### UI: the actions and the removed message

The message actions come from the `messageActions` function on `Channel` and `Thread`, by default the SDK's own. Each action reads the message it was built for and calls the state layer:

| Action                   | `actionType`  | What it calls                                                                                         | Shown when                                            |
| ------------------------ | ------------- | ----------------------------------------------------------------------------------------------------- | ----------------------------------------------------- |
| Flag Message             | `flagMessage` | Asks to confirm, then `client.moderation.flagMessage({ message_id: message.id })`                     | Not the user's own message, and `flag-message`        |
| Mute User, Unmute User   | `muteUser`    | `client.moderation.mute({ target_ids })` or `client.moderation.unmute({ target_ids })` for the author | Not the user's own message                            |
| Ban User, Unban User     | `banUser`     | `client.moderation.ban({ target_user_id })` or `client.unbanUser()` for the author                    | Not the user's own message, and `ban-channel-members` |
| Block User, Unblock User | `blockUser`   | Asks to confirm a block, then `client.blockUser()` or `client.unblockUser()` for the author           | Not the user's own message                            |

Ban User calls `client.moderation.ban()` without a channel, so it bans the author from the whole app, and the server checks that the user may do that. It reads Unban User when the message's `user.banned` is set.

Flag Message and Mute User report their result as a notification in the message list. Ban User and Block User report nothing. [Message actions](https://getstream.io/chat/docs/sdk/react-native/v10/features/messaging/message-actions/) lists every message action.

The channel list rows and channel details have their own actions, which call the same methods. Their tables are on [Channel lists](https://getstream.io/chat/docs/sdk/react-native/v10/features/channels/channel-lists/#the-row-actions) and [Channel details and members](https://getstream.io/chat/docs/sdk/react-native/v10/features/channels/channel-details-and-members/#turning-it-on-or-off). The user actions behind them are also a hook: `useUserActions(user)` returns `muteUser`, `unmuteUser`, `blockUser` and `unblockUser` for any user, each publishing a notification. See [Running channel and user actions](https://getstream.io/chat/docs/sdk/react-native/v10/ui/customizing/actions/#running-channel-and-user-actions).

`Message` renders `MessageBlocked` instead of the message when it's an `error` message whose moderation action is `remove`. The default shows the message's text in a small centered notice. It opens no menu: tapping and long-pressing it do nothing. `isBlockedMessage(message)` is the same check, exported for your own components.

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-native/v10/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 Message** needs `flag-message`.
- **Ban User** needs `ban-channel-members`.
- **Mute User** and **Block User** need no capability, in the message menu, in the channel list and in channel details, because they're personal. The channel list's mute button and the channel details switch check no capability either.

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/).

Each rule of a moderation policy has an action, chosen on the **Policies** page of the [Dashboard](https://getstream.io/signin/?product=chat). The table shows what each action does, and what the message's author sees:

| Action            | What the server does                                                                                    | What the author sees                                                                                                                                                                                                 |
| ----------------- | ------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Flag              | Publishes the message, and adds it to the review queue.                                                 | The message, as usual.                                                                                                                                                                                               |
| Remove            | Doesn't publish the message, and adds it to the review queue.                                           | `MessageBlocked`, a small centered notice, in place of the message.                                                                                                                                                  |
| Bounce            | Returns the message to its author without publishing or storing it.                                     | The message in an error style. Tapping it offers to send it anyway, edit it or delete it. See [Bounced messages](https://getstream.io/chat/docs/sdk/react-native/v10/features/messaging/sending-editing-and-deleting/#bounced-messages). |
| Bounce and flag   | Bounces the message. If the author sends it again and it still breaks the rule, publishes and flags it. | The bounced message, then the message, as usual.                                                                                                                                                                     |
| Bounce and remove | Bounces the message. If the author sends it again and it still breaks the rule, removes it.             | The bounced message, then `MessageBlocked`.                                                                                                                                                                          |

A bounced message isn't stored, so it doesn't reach the review queue, which holds only flagged and removed messages. [Bounce message action](https://getstream.io/moderation/docs/node/content-moderation/actions/#bounce) and [Policies](https://getstream.io/moderation/docs/node/configuration/policies/) in the Moderation docs cover the actions.

To hide an action from users who have the capability, pass `overrideOwnCapabilities` to `Channel` and `Thread`. This hides Flag Message and Ban User:

```tsx
<Channel
  channel={channel}
  overrideOwnCapabilities={{ banChannelMembers: false, flagMessage: false }}
>
```

To remove Mute User or Block User, filter them out of the list in your own `messageActions`, as [Message actions](https://getstream.io/chat/docs/sdk/react-native/v10/features/messaging/message-actions/#customizing) does for Block User.

## Customizing

### Flagging with a reason

This menu replaces Flag Message with **Report spam**, which flags the message with a reason and without the confirmation. The action keeps the `flagMessage` type, so the default rules still show it only to users with `flag-message`:

```tsx title="src/ChannelScreen.tsx"
import { useCallback } from "react";
import type { Channel as StreamChannel } from "stream-chat";
import {
  Channel,
  MessageComposer,
  MessageList,
  messageActions as defaultMessageActions,
  useChatContext,
} from "stream-chat-react-native";
import type { MessageActionsProp } from "stream-chat-react-native";

export const ChannelScreen = ({ channel }: { channel: StreamChannel }) => {
  const { client } = useChatContext();

  const messageActions = useCallback<MessageActionsProp>(
    (params) => {
      const report = async () => {
        try {
          await client.moderation.flagMessage({
            message_id: params.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 defaultMessageActions(params).map((action) =>
        action.actionType === "flagMessage"
          ? { ...action, action: () => void report(), title: "Report spam" }
          : action,
      );
    },
    [client],
  );

  return (
    <Channel channel={channel} messageActions={messageActions}>
      <MessageList />
      <MessageComposer />
    </Channel>
  );
};
```

The notifications carry no target, so they show in the notification list that mounted last, the message list's. [Actions](https://getstream.io/chat/docs/sdk/react-native/v10/ui/customizing/actions/#message-actions) explains the action objects, and the actions in the channel list and in channel details.

### The removed message

Replace `MessageBlocked` through `WithComponents`. It receives the message as `message`:

```tsx title="src/RemovedMessage.tsx"
import type { PropsWithChildren } from "react";
import { StyleSheet, Text } from "react-native";
import { WithComponents } from "stream-chat-react-native";
import type { MessageBlockedProps } from "stream-chat-react-native";

const RemovedMessage = ({ message }: MessageBlockedProps) => (
  <Text style={styles.text}>
    {message.user?.name ?? "This message"} was removed by moderation.
  </Text>
);

// Declared outside the component, so WithComponents gets the same object on every render.
const overrides = { MessageBlocked: RemovedMessage };

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

const styles = StyleSheet.create({
  text: { fontStyle: "italic", padding: 8, textAlign: "center" },
});
```

### 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 title="src/BlockedBadge.tsx"
import { Text } from "react-native";
import type { BlockedUsersState } from "stream-chat";
import { useChatContext, useStateStore } from "stream-chat-react-native";

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

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

  return userIds.includes(userId) ? <Text>Blocked</Text> : null;
};
```

## Reference

- [Message UI components](https://getstream.io/chat/docs/sdk/react-native/v10/component-reference/message/message-ui-components/): `MessageBlocked` and `MessageBounce`.
- [ComponentsContext](https://getstream.io/chat/docs/sdk/react-native/v10/component-reference/channel/componentscontext/): the `MessageBlocked`, `MessageActionList` and `MessageActionListItem` override keys.
- [Channel](https://getstream.io/chat/docs/sdk/react-native/v10/component-reference/channel/channel/): `messageActions`, `overrideOwnCapabilities`, and `handleFlag`, `handleMute`, `handleBan` and `handleBlockUser`.
- [Message hooks](https://getstream.io/chat/docs/sdk/react-native/v10/reference/hooks/message-hooks/): `useMessageActions` and `useMessageActionHandlers`.
- [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-native/v10/features/moderation-and-safety/flagging-muting-blocking-and-bans/](https://getstream.io/chat/docs/sdk/react-native/v10/features/moderation-and-safety/flagging-muting-blocking-and-bans/).