# Moderation Tools

## Flag

Any user is allowed to flag a message. This adds the message to the inbox of your moderation review queue on Stream Dashboard.

<codetabs>

<codetabs-item value="kotlin" label="Kotlin">

```kotlin
client.flagMessage(
  messageId = "message-id",
  reason = "This message is inappropriate",
  customData = mapOf("extra_info" to "more details"),
).enqueue { result ->
  if (result.isSuccess) {
    // Message was flagged
    val flag: Flag = result.data()
  } else {
    // Handle result.error()
  }
}

client.flagUser(
  userId = "user-id",
  reason = "This user is a spammer",
  customData = mapOf("extra_info" to "more details"),
).enqueue { result ->
  if (result.isSuccess) {
    // User was flagged
    val flag: Flag = result.data()
  } else {
    // Handle result.error()
  }
}
```

</codetabs-item>

<codetabs-item value="javascript" label="JavaScript">

```js
const flag = await client.flagMessage(messageResponse.message.id);
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
$flag = $client->flagMessage('message-id', ['user_id' => 'user-id']);
```

</codetabs-item>

<codetabs-item value="dart" label="Dart">

```dart
await client.flagMessage("messageID");
```

</codetabs-item>

<codetabs-item value="swift" label="Swift">

```swift
import StreamChat

// FLAG/UNFLAG MESSAGES

/// 1: Create a `ChannelId` that represents the channel you want to get a message from.
let channelId = ChannelId(type: .messaging, id: "general")

/// 2: Create a `MessageId` that represents the message you want to flag.
let messageId = "message-id"

/// 3: Use the `ChatClient` to create a `ChatMessageController` with the `ChannelId` and message id.
let messageController = chatClient.messageController(cid: channelId, messageId: messageId)

/// 4: Call `ChatMessageController.flag` to flag the message.
messageController.flag { error in
  // handle possible errors / access message
  print(error ?? "message flagged")

  /// 5: Call `ChatMessageController.unflag` to unflag the message.
  messageController.unflag { error in
    print(error ?? "message unflagged")
  }
}

// FLAG/UNFLAG USERS

let userController = chatClient.userController(userId: "another_user")

/// 1: Call `ChatUserController.flag` to flag the user.
userController.flag { error in
  // handle possible errors
  print(error ?? "user flagged")

  /// 2: Call `ChatUserController.unflag` to unflag the user.
  userController.unflag { error in
    print(error ?? "user unflagged")
  }
}
```

</codetabs-item>

<codetabs-item value="unreal" label="Unreal">

```cpp
Client->FlagMessage(Message);
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
client.flag_message(msg["id"], user_id=server_user["id"])
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
# require 'stream-chat'

@client.flag_message(msg_id, user_id: server_user[:id])
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
client.FlagMessage(ctx, msg.ID, user.ID)
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
await flagClient.FlagMessageAsync(message.Id, user.Id);
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
client.flagMessage(
    "message-id",
    "This message is inappropriate",
    Map.of("extra_info", "more details")
).enqueue(result -> {
  if (result.isSuccess()) {
    // Message was flagged
    Flag flag = result.data();
   } else {
    // Handle result.error()
  }
});

client.flagUser(
    "user-id",
    "This user is a spammer",
    Map.of("extra_info", "more details")
).enqueue(result -> {
  if (result.isSuccess()) {
    // User was flagged
    Flag flag = result.data();
   } else {
    // Handle result.error()
  }
});
```

</codetabs-item>

<codetabs-item value="unity" label="Unity">

```csharp
// Flag a message
await message.FlagAsync();

// Flag a user
await channelMember.User.FlagAsync();
```

</codetabs-item>

</codetabs>

### Reasons & custom data

You can enhance flags by associating them with a specific reason and custom data. It is advisable to utilise a slug or keyword as a designated reason for easy translation or other forms of display customisation.

The custom data can encompass any object, offering supplementary metadata to the flag.

The Query Message Flags endpoint retrieves both reasons and custom data, and the reason can also be utilised for filtering these flags.

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

```js
// flag with a reason
let flag = await client.flagMessage(messageID, {
  reason: "spammy_user",
});

// flag with a reason and additional custom data
flag = await client.flagMessage(messageID, {
  reason: "spammy_user",
  custom: {
    user_comment: "This user is spamming the homepage.",
    page: "homepage",
  },
});

// flag with only custom data
flag = await client.flagMessage(messageID, {
  custom: {
    page: "homepage",
  },
});
cf;
```

</codetabs-item>

<codetabs-item value="swift" label="Swift">

```swift
// flag with a reason
messageController.flag(reason: "spammy_user")
```

</codetabs-item>

</codetabs>

### Query Flagged Messages

If you prefer to build your own in-app moderation dashboard, rather than use the Stream dashboard, you can query flagged messages using the `QueryReviewQueue` API endpoint.

Both server-authenticated and user-authenticated clients can use this method. For client-side requests, the user needs moderator or admin permissions.

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

```js
const response = await client.moderation.queryReviewQueue(
  { entity_type: "stream:chat:v1:message" },
  [{ field: "created_at", direction: -1 }],
  { next: null },
);

for (const item of response.items) {
  console.log(item.message.id);
  console.log(item.message.text);
  console.log(item.message.type);
  console.log(item.message.created_at);
}

console.log(next); // <-- next cursor for pagination
```

</codetabs-item>

</codetabs>

Please refer to the [Moderation API](/moderation/docs/api/#query-review-queue) documentation for more details.

### Mutes

Any user is allowed to mute another user. Mutes are stored at the user level and returned with the rest of the user information when `connectUser` is called. A user will be muted until the user is _unmuted_ or the mute is expired.

| name    | type   | description                                       | default  | optional |
| ------- | ------ | ------------------------------------------------- | -------- | -------- |
| timeout | number | The timeout in minutes until the mute is expired. | no limit | ✓        |

<codetabs>

<codetabs-item value="kotlin" label="Kotlin">

```kotlin
client.muteUser("user-id").enqueue { result ->
  if (result.isSuccess) {
    // User was muted
    val mute: Mute = result.data()
  } else {
    // Handle result.error()
  }
}

client.unmuteUser("user-id").enqueue { result ->
  if (result.isSuccess) {
    // User was unmuted
  } else {
    // Handle result.error()
  }
}
```

</codetabs-item>

<codetabs-item value="javascript" label="JavaScript">

```js
// client-side mute
const mute = await client.muteUser("eviluser");

// client-side mute for 60 minutes
const mute = await client.muteUser("eviluser", null, {
  timeout: 60,
});

// server-side mute requires the id of the user creating the mute as well
const mute = await client.muteUser("eviluser", "user_id");
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
//

$mute = $client->muteUser('evil-user', 'user-id');
```

</codetabs-item>

<codetabs-item value="dart" label="Dart">

```dart
await client.muteUser("eviluser");

await client.unmuteUser("eviluser");
```

</codetabs-item>

<codetabs-item value="swift" label="Swift">

```swift
import StreamChat

let userController = chatClient.userController(userId: "another_user")

/// 1: Call `ChatUserController.mute` to mute the user.
userController.mute { error in
  // handle possible errors
  print(error ?? "user muted")

  /// 2: Call `ChatUserController.unmute` to unmute the user.
  userController.unmute { error in
    print(error ?? "user unmuted")
  }
}
```

</codetabs-item>

<codetabs-item value="unreal" label="Unreal">

```cpp
Client->MuteUser(User);

Client->UnmuteUser(User);
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
# server-side mute requires the id of the user creating the mute as well
client.mute_user(target_user["id"], server_user["id"])
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
# server-side mute requires the id of the user creating the mute as well
client.mute_user(target_user["id"], server_user["id"])
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
// server-side mute requires the id of the user creating the mute as well
client.MuteUser(ctx, targetUser.ID, serverUser.ID)
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
// server-side mute requires the id of the user creating the mute as well
await userClient.MuteAsync(targetUser.Id, serverUser.Id);
```

</codetabs-item>

<codetabs-item value="unity" label="Unity">

```csharp
// Mute a user
await channelMember.User.MuteAsync();

// Unmute previously muted user
await channelMember.User.UnmuteAsync();

// Mute a channel
await channel.MuteChannelAsync();

// Mute previously muted channel
await channel.UnmuteChannelAsync();
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
client.muteUser("user-id").enqueue(result -> {
  if (result.isSuccess()) {
    // User was muted
    Mute mute = result.data();
  } else {
    // Handle result.error()
  }
});

client.unmuteUser("user-id").enqueue(result -> {
  if (result.isSuccess()) {
    // User was unmuted
  } else {
    // Handle result.error()
  }
});
```

</codetabs-item>

</codetabs>

After muting a user messages will still be delivered via web-socket. Implementing business logic such as hiding messages from muted users or displaying them differently is left to the developer to implement.

<admonition type="info">

Messages from muted users are not delivered via push (APN/Firebase)

</admonition>

### Ban

Users can be banned from an app entirely or from a channel. When a user is banned, they will not be allowed to post messages until the ban is removed or expired but will be able to connect to Chat and to channels as before.

Channel watchers cannot be banned.

It is also possible to ban the user's last known IP address to prevent the creation of new "throw-away" accounts. This type of ban is only applicable on the app level. ISPs often rotate IP addresses, therefore in order to prevent innocent users to be banned, we automatically apply a 30 day timeout when applying an IP ban. This value can be overwritten, if a timeout is supplied in the request. The IP address will be unbanned either after reaching a timeout or with explicit user unban.

In most cases, only admins or moderators are allowed to ban other users from a channel.

| name            | type    | description                                                                                                               | default  | optional |
| --------------- | ------- | ------------------------------------------------------------------------------------------------------------------------- | -------- | -------- |
| timeout         | number  | The timeout in minutes until the ban is automatically expired.                                                            | no limit | ✓        |
| reason          | string  | The reason that the ban was created.                                                                                      |          | ✓        |
| ip_ban          | boolean | Whether or not to apply IP address ban                                                                                    | false    | ✓        |
| banned_by_id    | string  | The ID of the user who is performing the ban. This is required only when using API from the server-side                   |          | ✓        |
| delete_messages | string  | Delete messages of the banned user. Can be "soft" or "hard" which soft-deletes and hard-deletes the messages respectively |          | ✓        |

<admonition type="info">

Banning a user from all channels can only be done using server-side auth.

</admonition>

<codetabs>

<codetabs-item value="kotlin" label="Kotlin">

```kotlin
// Ban user for 60 minutes from a channel
channelClient.banUser(targetId = "user-id", reason = "Bad words", timeout = 60).enqueue { result ->
  if (result.isSuccess) {
    // User was banned
  } else {
    // Handle result.error()
  }
}

channelClient.unBanUser(targetId = "user-id").enqueue { result ->
  if (result.isSuccess) {
    // User was unbanned
  } else {
    // Handle result.error()
  }
}
```

</codetabs-item>

<codetabs-item value="javascript" label="JavaScript">

```js
// ban a user for 60 minutes from all channel
let data = await client.banUser("eviluser", {
  banned_by_id: userID, // ID of the user who is performing the ban (Server-side auth)
  timeout: 60,
  reason: "Banned for one hour",
});

// ban a user and their IP address for 24 hours
let data = await client.banUser("eviluser", {
  banned_by_id: userID,
  timeout: 24 * 60,
  ip_ban: true,
  reason: "Please come back tomorrow",
});

// ban a user from the livestream:fortnite channel
data = await channel.banUser("eviluser", {
  banned_by_id: userID,
  reason: "Profanity is not allowed here",
});

// remove ban from channel
data = await channel.unbanUser("eviluser");

// remove global ban
data = await authClient.unbanUser("eviluser");
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
# ban a user for 60 minutes from all channel
client.ban_user("eviluser", banned_by_id=admin_id, timeout=60, reason="Banned for one hour")

# ban a user and their IP address for 24 hours
client.ban_user("eviluser", banned_by_id=admin_id, timeout=24*60, ip_ban=true, reason="Please come back tomorrow")

# ban a user from the livestream:fortnite channel and also soft delete the messages
channel.ban_user("eviluser", banned_by_id=admin_id, reason="Profanity is not allowed here", delete_messages="soft")

# remove ban from channel
channel.unban_user("eviluser")

# remove global ban
client.unban_user("eviluser")
```

</codetabs-item>

<codetabs-item value="swift" label="Swift">

```swift
import StreamChat

/// 1: Create a `ChannelId` that represents the channel you want to ban a user from.
let channelId = ChannelId(type: .messaging, id: "general")

/// 2: Create a `ChatChannelMemberController` to make operations on a channel member.
let controller = chatClient.memberController(userId: "another_user", in: channelId)

/// 3: Call `ChatChannelMemberController.ban` to ban the user from the channel.
controller.ban { error in
  // handle possible errors
  print(error ?? "user banned")

  /// 4: Call `ChatUserController.unmute` to unmute the user.
  controller.unban { error in
    print(error ?? "user unmuted")
  }
}
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
// ban a user for 60 minutes from all channel
$ban = $client->banUser('evil-user', [
  'timeout' => 60,
  'reason' => 'Banned for one hour',
  'banned_by_id' => 'user-id'
]);

// ban a user and their IP address for 24 hours
$ban = $channel->banUser('evil-user', [
  'banned_by_id' => 'user-id'
  'timeout' => 24*60,
  'ip_ban' => true,
  'reason' => 'Please come back tomorrow',
]);

// ban a user from the livestream:fortnite channel
$ban = $channel->banUser('evil-user', [
  'reason' => 'Banned for one hour',
  'banned_by_id' => 'user-id'
]);

// remove ban from channel
$unban = $channel->unbanUser('evil-user');

// remove global ban
$unban = $client->unbanUser('evil-user');
```

</codetabs-item>

<codetabs-item value="dart" label="Dart">

```dart
// ban a user for 60 minutes from all channel
await client.banUser('eviluser', {
  'banned_by_id': userID, // ID of the user who is performing the ban (Server-side auth)
  'timeout': 60,
  'reason': 'Banned for one hour',
});

// ban a user and their IP address for 24 hours
 await client.banUser('eviluser', {
  'banned_by_id': userID,
  'timeout': 24*60,
  'ip_ban': true,
  'reason': 'Please come back tomorrow',
});

// ban a user from the livestream:fortnite channel
await channel.banMember(
  'banned_by_id': userID,
  'reason': 'Profanity is not allowed here',
});

// remove ban from channel
await channel.banMember('eviluser');

// remove global ban
await client.unbanUser('eviluser');
```

</codetabs-item>

<codetabs-item value="unreal" label="Unreal">

```cpp
// ban a user for 60 minutes from all channel
Client->BanUser(User, FTimespan::FromMinutes(60.), {TEXT("Banned for one hour")});

// ban a user and their IP address for 24 hours
Client->BanUser(User, FTimespan::FromHours(24.), {TEXT("Please come back tomorrow")}, true);

// ban a user from a specific channel
Channel->BanMember(User, {}, {TEXT("Profanity is not allowed here")});

// remove ban from channel
Channel->UnbanMember(User);

// remove global ban
Client->UnbanUser(User);
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
# ban a user for 60 minutes from all channel
client.ban_user("eviluser", banned_by_id: admin_id, timeout: 60, reason: "Banned for one hour")

# ban a user and their IP address for 24 hours
client.ban_user("eviluser", banned_by_id: admin_id, timeout: 24*60, ip_ban: true, reason: "Please come back tomorrow")

# ban a user from the livestream:fortnite channel
channel.ban_user("eviluser", banned_by_id: admin_id, reason: "Profanity is not allowed here")

# remove ban from channel
channel.unban_user("eviluser")

# remove global ban
client.unban_user("eviluser")
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
// ban from channel
channel.BanUser(ctx, target.ID, bannedBy.ID, BanWithReason("spammer"), BanWithExpiration(60))

// ban globally
client.BanUser(ctx, target.ID, bannedBy.ID, BanWithReason("spammer"), BanWithExpiration(60))

// unban from channel
channel.UnBanUser(ctx, target.ID)

// unban globally
client.UnBanUser(ctx, target.ID)
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
// ban from channel
await userClient.BanAsync(new BanRequest
{
  Type = channel.Type,
  Id = channel.Id,
  Reason = "reason",
  TargetUserId = targetUser.Id,
  UserId = bannedBy.Id,
});

// ban globally
await userClient.BanAsync(new BanRequest
{
  Reason = "reason",
  TargetUserId = targetUser.Id,
  UserId = bannedBy.Id,
});

// unban from channel
await userClient.UnbanAsync(new BanRequest
{
  Type = channel.Type,
  Id = channel.Id,
  TargetUserId = targetUser.Id,
  UserId = bannedBy.Id,
});

// unban globally
await userClient.UnbanAsync(new BanRequest
{
  TargetUserId = targetUser.Id,
  UserId = bannedBy.Id,
});
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
// Ban user for 60 minutes from a channel
channelClient.banUser("user-id", "Bad words", 60).enqueue(result -> {
  if (result.isSuccess()) {
    // User was banned
  } else {
    // Handle result.error()
  }
});

channelClient.unbanUser("user-id").enqueue(result -> {
  if (result.isSuccess()) {
    // User was unbanned
  } else {
    // Handle result.error()
  }
});
```

</codetabs-item>

<codetabs-item value="unity" label="Unity">

```csharp
// Dummy example to get IStreamUser to ban
var user = channel.Messages.First().User;

// Dummy example to get IStreamUser to ban
var channelMember = channel.Members.First();

// Ban a user permanently from this channel permanently
await channel.BanUserAsync(user);

// Use any combination of the optional parameters: reason, timeoutMinutes, isIpBan

// Ban a user from this channel for 2 hours with a reason
await channel.BanUserAsync(user, "You got banned for 2 hours for toxic behaviour.", 120);

// Ban a user IP from this channel for 2 hours without a reason
await channel.BanUserAsync(user, timeoutMinutes: 120, isIpBan: true);

// Ban a member from this channel permanently
await channel.BanMemberAsync(channelMember);
```

</codetabs-item>

</codetabs>

### Query Banned Users

Banned users can be retrieved in different ways:

1. Using the dedicated query bans endpoint

2. User Search: you can add the `banned: true` condition to your search. Please note that this will only return users that were banned at the app level and not the ones that were banned only on channels.

<codetabs>

<codetabs-item value="kotlin" label="Kotlin">

```kotlin
// Retrieve the list of banned users
client.queryUsers(
  QueryUsersRequest(
    filter = Filters.eq("banned", true),
    offset = 0,
    limit = 10,
  )
).enqueue { result ->
  if (result.isSuccess) {
    val users: List<User> = result.data()
  } else {
    // Handle result.error()
  }
}

// Query for banned members from one channel
client.queryBannedUsers(filter = Filters.eq("channel_cid", "ChannelType:ChannelId")).enqueue { result ->
  if (result.isSuccess) {
    val bannedUsers: List<BannedUser> = result.data()
  } else {
    // Handle result.error()
  }
}
```

</codetabs-item>

<codetabs-item value="javascript" label="JavaScript">

```js
// retrieve the list of banned users
const response = await client.queryUsers(
  { banned: true },
  {},
  { limit: 10, offset: 0 },
);

// query for banned members from one channel
const results = await client.queryBannedUsers({
  channel_cid: "livestream:123",
});
```

</codetabs-item>

<codetabs-item value="dart" label="Dart">

```dart
// retrieve the list of banned users
await client.queryUsers(
  filter: Filter.equal('banned', true),
 );
```

</codetabs-item>

<codetabs-item value="swift" label="Swift">

```swift
let userListController = client.userListController(query: .init(filter: .equal(.isBanned, to: true)))

userListController.synchronize() { error in
  if error == nil {
    userListController.users
  }
}
```

</codetabs-item>

<codetabs-item value="unreal" label="Unreal">

```cpp
// Retrieve the list of banned users
Client->QueryUsers(
  FFilter::Equal(TEXT("banned"), true),
  {},   // Sort options
  true,  // Presence
  {},   // Pagination options
  [](const TArray<FUserRef> Users)
  {
    // Do something with Users
  });

// Query for banned members from one channel
Client->QueryBannedUsers(
  FFilter::Equal(TEXT("channel_cid"), TEXT("ChannelType:ChannelId")),
  {},  // Sort options
  {},  // Pagination options
  [](const TArray<FBan> Bans)
  {
    // Do something with Bans
  });
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
$client->queryBannedUsers(["channel_cid" => "ChannelType:ChannelId"]);
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
client.query_banned_users(
  {"filter_conditions": {"channel_cid": "ChannelType:ChannelId"}}
)
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
client.query_banned_users({ 'channel_cid' => 'ChannelType:ChannelId' })
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
client.QueryBannedUsers(ctx, &QueryBannedUsersOptions{
	QueryOption: &QueryOption{Filter: map[string]interface{}{
		"channel_cid": "ChannelType:ChannelId",
	}},
})
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
await userClient.QueryBannedUsersAsync(new QueryBannedUsersRequest
{
  FilterConditions = new Dictionary<string, object> { { "channel_cid", "ChannelType:ChannelId" } },
});
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
// Retrieve the list of banned users
FilterObject filter = Filters.eq("banned", true);
QueryUsersRequest request = new QueryUsersRequest(filter, 0, 10);
client.queryUsers(request).enqueue(result -> {
  if (result.isSuccess()) {
    List<User> users = result.data();
  } else {
    // Handle result.error()
  }
});

// Query for banned members from one channel
FilterObject channelFilter = Filters.eq("channel_cid", "ChannelType:ChannelId");
client.queryBannedUsers(channelFilter).enqueue(result -> {
  if (result.isSuccess()) {
    List<BannedUser> bannedUsers = result.data();
  } else {
    // Handle result.error()
  }
});
```

</codetabs-item>

<codetabs-item value="unity" label="Unity">

```csharp
// Get users banned in the last 24 hours
var request = new StreamQueryBannedUsersRequest
{
  CreatedAtAfterOrEqual = new DateTimeOffset().AddHours(-24),
  Limit = 30,
  Offset = 0,
};

var bannedUsersInfo = await Client.QueryBannedUsersAsync(request);
```

</codetabs-item>

</codetabs>

### Query Bans Endpoint

The query bans endpoint allows you to list bans for your application. Similar to other query endpoints, you can filter bans by different fields and control the ordering of results.

<admonition type="info">

Globally banned users can only be retrieved using server-side auth

</admonition>

#### Available fields

| Name         | Description                                                                                                      | Example                                               | Operators                 |
| ------------ | ---------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | ------------------------- |
| channel_cid  | The channel CID for the ban. When this parameter is not provided, both global and channel bans will be returned. | { channel_cid :{$in:["livestream:1","livestream:2"]}} | $eq, $in                  |
| user_id      | The ID of the banned user                                                                                        | { user_id: "evil_user" }                              | $eq, $in, $neq, $nin      |
| created_at   | The date (RFC339) of the ban creation                                                                            | { created_at: {$gt: "2020-10-02T15:00:00Z"} }         | $eq, $gt, $gte, $lt, $lte |
| banned_by_id | The ID of the user that created the ban                                                                          | { banned_by_id: "42"}                                 | $eq, $in, $neq, $nin      |

Pagination for bans can be done in two ways: using offset/limit or using the `created_at` field. Bans are returned in ascending order by default so to get the second page you need to request bans with `created_at` less than the `created_at` of the last ban on the first page. Ordering can be reversed using the sort option.

<codetabs>

<codetabs-item value="kotlin" label="Kotlin">

```kotlin
// Get the bans for channel livestream:123 in descending order
client.queryBannedUsers(
  filter = Filters.eq("channel_cid", "livestream:123"),
  sort = QuerySortByField.descByName("createdAt"),
).enqueue { result ->
  if (result.isSuccess) {
    val bannedUsers: List<BannedUser> = result.data()
  } else {
    // Handle result.error()
  }
}

// Get the page of bans which where created before or equal date for the same channel
client.queryBannedUsers(
  filter = Filters.eq("channel_cid", "livestream:123"),
  sort = QuerySortByField.descByName("createdAt"),
  createdAtBeforeOrEqual = Date(),
).enqueue { result ->
  if (result.isSuccess) {
    val bannedUsers: List<BannedUser> = result.data()
  } else {
    // Handle result.error()
  }
}
```

</codetabs-item>

<codetabs-item value="javascript" label="JavaScript">

```js
// get the bans for channel livestream:123 in descending order
const response = await client.queryBannedUsers(
  { channel_cid: "livestream:123" },
  { created_at: -1 },
);

// get the next page of bans for the same channel
await client.queryBannedUsers(
  { channel_cid: "livestream:123", created_at_before_or_equal: createdAt },
  { created_at: -1 },
);
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
$opts = [
 "limit" => 1,
 "sort" => [
  [
   "field" => "created_at",
   "direction" => -1
  ]
 ]
];

$client->queryBannedUsers(["channel_cid" => "livestream:123"], $opts);
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
client.query_banned_users(
  {"filter_conditions": {"channel_cid": "livestream:123"}, "sort": [{"field": "created_at", "direction": -1}]}
)
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
client.query_banned_users({ 'channel_cid' => 'livestream:123' }, {"created_at": -1}, limit: 1)
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
await userClient.QueryBannedUsersAsync(new QueryBannedUsersRequest
{
  FilterConditions = new Dictionary<string, object> { { "channel_cid", "livestream:123" } },
  Sorts = new List<SortParameter> { new SortParameter { Field = "created_at", Direction = SortDirection.Descending } },
});
```

</codetabs-item>

<codetabs-item value="unreal" label="Unreal">

```cpp
// Get the bans for channel livestream:123 in reverse chronological order of banning
Client->QueryBannedUsers(
  FFilter::Equal(TEXT("channel_cid"), TEXT("livestream:123")),
  {{EBanSortField::CreatedAt, ESortDirection::Descending}},
  {},
  [](const TArray<FBan> Bans)
  {
    // Do something with Bans
  });

// Get the page of bans which were created before or equal to the current time for the same channel
FBanPaginationOptions PaginationOptions;
PaginationOptions.CreatedAtBeforeOrEqual = FDateTime::UtcNow();
Client->QueryBannedUsers(
  FFilter::Equal(TEXT("channel_cid"), TEXT("livestream:123")),
  {{EBanSortField::CreatedAt, ESortDirection::Descending}},
  {PaginationOptions},
  [](const TArray<FBan> Bans)
  {
    // Do something with Bans
  });
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
// Get the bans for channel livestream:123 in descending order
FilterObject filter = Filters.eq("channel_cid", "livestream:123");
QuerySorter<BannedUsersSort> sort = QuerySortByField.descByName("createdAt");
client.queryBannedUsers(filter, sort).enqueue(result -> {
  if (result.isSuccess()) {
    List<BannedUser> bannedUsers = result.data();
  } else {
    // Handle result.error()
  }
});

// Get the page of bans which where created before or equal date for the same channel
client.queryBannedUsers(filter, sort, null, null, null, null, null, new Date()).enqueue(result -> {
  if (result.isSuccess()) {
    List<BannedUser> bannedUsers = result.data();
  } else {
    // Handle result.error()
  }
});
```

</codetabs-item>

<codetabs-item value="unity" label="Unity">

```csharp
// Get users banned in the last 24 hours
var request = new StreamQueryBannedUsersRequest
{
  CreatedAtAfterOrEqual = new DateTimeOffset().AddHours(-24),
  Limit = 30,
  Offset = 0,
};

var bannedUsersInfo = await Client.QueryBannedUsersAsync(request);
```

</codetabs-item>

</codetabs>

### Shadow Ban

Instead of a default ban, you can *shadow ban*users from a channel, set of channels, or an entire App. When a user is shadow banned, they will still be allowed to post messages, but any message sent during the ban will only be visible to the author of the message and invisible to other users of the App.

Shadow banning can delay a persistent bad actor from attempting ban evasion techniques by obfuscating the fact that a ban has occurred for them. Since a shadow ban's effectiveness relies on it not being discovered, shadow bans work best in chats with a high volume of messages and fast velocity where the offending user's messages not receiving engagement appears plausible; think livestreams.

Messages from a shadow banned user will include the `shadowed: true` flag on the message object. This flag is only visible to users other than the shadow banned user, so the offender will not be able to recognize that they are shadow banned even if they use developer tools to inspect responses from the API. You will need to implement UI logic for how your application will handle shadowed messages. Having the client hide these messages for everybody other than the user sending them is a common approach.

| name         | type    | description                                                                                             | default  | optional |
| ------------ | ------- | ------------------------------------------------------------------------------------------------------- | -------- | -------- |
| timeout      | number  | The timeout in minutes until the ban is automatically expired.                                          | no-limit | ✓        |
| reason       | string  | The reason that the ban was created.                                                                    | -        | ✓        |
| ip_ban       | boolean | Whether or not to apply IP address ban                                                                  | false    | ✓        |
| banned_by_id | string  | The ID of the user who is performing the ban. This is required only when using API from the server-side | -        | ✓        |

<codetabs>

<codetabs-item value="kotlin" label="Kotlin">

```kotlin
// Shadow ban user for 60 minutes from a channel
channelClient.shadowBanUser(targetId = "user-id", reason = "Bad words", timeout = 60).enqueue { result ->
   if (result.isSuccess) {
     // User was shadow banned
   } else {
     // Handle result.error()
   }
}

channelClient.removeShadowBan("user-id").enqueue { result ->
  if (result.isSuccess) {
    // Shadow ban was removed
  } else {
    // Handle result.error()
  }
}
```

</codetabs-item>

<codetabs-item value="javascript" label="JavaScript">

```js
// shadow ban a user from all channels
let data = await client.shadowBan("eviluser");

// shadow ban a user from a channel
data = await channel.shadowBan("eviluser");

// remove shadow ban from channel
data = await channel.removeShadowBan("eviluser");

// remove global shadow ban
data = await client.removeShadowBan("eviluser");

// server-side shadow ban
data = await client.shadowBan("eviluser", { banned_by_id: "regularUser" });
```

</codetabs-item>

<codetabs-item value="dart" label="Dart">

```dart
// shadow ban a user from all channels
await client.shadowBan('eviluser');

// shadow ban a user from a channel
await channel.shadowBan('eviluser');

// remove shadow ban from channel
await channel.removeShadowBan('eviluser');

// remove global shadow ban
await client.removeShadowBan('eviluser');
```

</codetabs-item>

<codetabs-item value="unreal" label="Unreal">

```cpp
// shadow ban a user from all channels
Client->ShadowBanUser(User);

// shadow ban a user from a channel
Channel->ShadowBanMember(User);

// remove shadow ban from channel
Channel->ShadowUnbanMember(User);

// remove global shadow ban
Client->ShadowUnbanUser(User);
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
$client->shadowBan($targetUser["id"], ["user_id" => $user2["id"]]);

$client->removeShadowBan($targetUser["id"], ["user_id" => $user2["id"]]);
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
client.shadow_ban(target_user["id"], user_id=server_user["id"])

client.remove_shadow_ban(target_user["id"], user_id=server_user["id"])
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
client.shadow_ban(target_user["id"], user_id: server_user["id"])

client.remove_shadow_ban(target_user["id"], user_id: server_user["id"])
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
client.ShadowBan(ctx, targetUser.ID, bannedBy.ID)

client.UnBanUser(ctx, targetUser.ID)
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
await userClient.ShadowBanAsync(new ShadowBanRequest
{
  TargetUserId = targetUser.Id,
  UserId = bannedBy.Id,
});

await userClient.RemoveShadowBanAsync(new ShadowBanRequest
{
  TargetUserId = targetUser.Id,
});
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
// Shadow ban user for 60 minutes from a channel
channelClient.shadowBanUser("user-id", "Bad words", 60).enqueue(result -> {
  if (result.isSuccess()) {
    // User was shadow banned
  } else {
    // Handle result.error()
  }
});

channelClient.removeShadowBan("user-id").enqueue(result -> {
  if (result.isSuccess()) {
    // Shadow ban was removed
  } else {
    // Handle result.error()
  }
});
```

</codetabs-item>

<codetabs-item value="unity" label="Unity">

```csharp
// Dummy example to get IStreamUser to ban
var user = channel.Messages.First().User;

// Dummy example to get IStreamUser to ban
var channelMember = channel.Members.First();

// Shadow Ban a user from this channel permanently
await channel.ShadowBanUserAsync(user);

// Shadow Ban a member from this channel
await channel.ShadowBanMemberAsync(channelMember);

// Use any combination of optional parameters: reason, timeoutMinutes, isIpBan

// Shadow Ban a member from this channel permanently
await channel.ShadowBanMemberAsync(channelMember);

// Shadow Ban a member from this channel for 2 hours with a reason
await channel.ShadowBanMemberAsync(channelMember, "Banned for 2 hours for toxic behaviour.", 120);

// Shadow Ban a member IP from this channel for 2 hours without a reason
await channel.ShadowBanMemberAsync(channelMember, timeoutMinutes: 120, isIpBan: true);
```

</codetabs-item>

</codetabs>

Administrators can view shadow banned user status in `queryChannels()` , `queryMembers()` and `queryUsers()` .

### Block Lists

A list of words you can define to moderate chat messages. A blocklist can be assigned to each channel type to either block or flag messages that contain these words. More information can be found [here](/chat/docs/<framework>/block_lists/).

### Automated Moderation for Chat

Stream AutoMod uses an AI-based classification system to detect various types of bad content. The tool is powered by AI, an is highly configurable for each channel type to assist the manual work of human moderators. You can learn more [here](/moderation/docs/quick-start/auto-moderation/).

---

This page was last updated at 2025-08-15T14:15:09.823Z.

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