# Moderation for Chat

Moderation is essential for a good user experience on chat.
There are also requirements from DSA and the app stores to take into account.

Stream has advanced AI moderation capabilities for text, images, video and audio.
Before we launched moderation, customers often struggled with the cost and difficulty of integrating external moderation APIs.
You can now setup moderation in minutes at an affordable price point.

There are 4 layers of moderation:

- **Limits / chat features**: Restrict what's allowed. Moderator permissions, disabling links or images, slow mode, enforce_unique_usernames, slash commands etc.
- **Simple**: Blocklist, regex, domain allow/block, email allow/block
- **User actions**: Flag, mute, ban etc.
- **[AI moderation](/moderation/docs/)**: AI on text, images, video, audio

Let's go over each of these and show what's supported.

## Limits & Chat features

### Disabling the permission to post links or add attachments

You can control links and attachments by revoking the relevant permissions for a role. The permissions to manage are:

- `add-links` - ability to post messages containing URLs
- `create-attachment` - ability to add attachments to messages
- `upload-attachment` - ability to upload files/images

Update the grants for a channel type to remove these permissions from a role:

<codetabs>

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

```js
// Remove link and attachment permissions for channel_member role
await client.updateChannelType("messaging", {
  grants: {
    channel_member: [
      "read-channel",
      "create-message",
      "update-message-owner",
      "delete-message-owner",
      // "add-links" - removed to disable links
      // "create-attachment" - removed to disable attachments
      // "upload-attachment" - removed to disable uploads
    ],
  },
});
```

</codetabs-item>

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

```python
# Remove link and attachment permissions for channel_member role
client.chat.update_channel_type(
    name="messaging",
    automod="disabled",
    automod_behavior="flag",
    max_message_length=5000,
    grants={
        "channel_member": [
            "read-channel",
            "create-message",
            "update-message-owner",
            "delete-message-owner",
            # "add-links" - removed to disable links
            # "create-attachment" - removed to disable attachments
            # "upload-attachment" - removed to disable uploads
        ],
    },
)
```

</codetabs-item>

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

```php
// Remove link and attachment permissions for channel_member role
$client->updateChannelType('messaging', new Models\UpdateChannelTypeRequest(
    automod: 'disabled',
    automodBehavior: 'flag',
    maxMessageLength: 5000,
    grants: [
        'channel_member' => [
            'read-channel',
            'create-message',
            'update-message-owner',
            'delete-message-owner',
            // 'add-links' - removed to disable links
            // 'create-attachment' - removed to disable attachments
            // 'upload-attachment' - removed to disable uploads
        ],
    ],
));
```

</codetabs-item>

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

```go
// Remove link and attachment permissions for channel_member role
client.Chat().UpdateChannelType(ctx, "messaging", &getstream.UpdateChannelTypeRequest{
  Automod:          "disabled",
  AutomodBehavior:  "flag",
  MaxMessageLength: 5000,
  Grants: map[string][]string{
    "channel_member": {
      "read-channel",
      "create-message",
      "update-message-owner",
      "delete-message-owner",
      // "add-links" - removed to disable links
      // "create-attachment" - removed to disable attachments
      // "upload-attachment" - removed to disable uploads
    },
  },
})
```

</codetabs-item>

</codetabs>

For more details on permissions, see [User Permissions](/chat/docs/<framework>/chat_permission_policies/).

### Image & Video file types

You can restrict which file types users can upload using `image_upload_config` and `file_upload_config`. This allows you to set allowed or blocked file extensions and MIME types, as well as size limits.

Both configs accept the following fields:

| Field                     | Description                                                       |
| ------------------------- | ----------------------------------------------------------------- |
| `allowed_file_extensions` | Array of allowed file extensions (e.g., `[".jpg", ".png"]`)       |
| `blocked_file_extensions` | Array of blocked file extensions                                  |
| `allowed_mime_types`      | Array of allowed MIME types (e.g., `["image/jpeg", "image/png"]`) |
| `blocked_mime_types`      | Array of blocked MIME types                                       |
| `size_limit`              | Maximum file size in bytes (default allows up to 100MB)           |

<codetabs>

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

```js
// Restrict images to common formats only
await client.updateAppSettings({
  image_upload_config: {
    allowed_file_extensions: [".jpg", ".jpeg", ".png", ".gif", ".webp"],
    allowed_mime_types: ["image/jpeg", "image/png", "image/gif", "image/webp"],
    size_limit: 5 * 1024 * 1024, // 5MB
  },
  // Restrict file uploads to documents only
  file_upload_config: {
    allowed_file_extensions: [".pdf", ".doc", ".docx"],
    allowed_mime_types: ["application/pdf", "application/msword"],
    size_limit: 10 * 1024 * 1024, // 10MB
  },
});
```

</codetabs-item>

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

```python
from getstream.models import FileUploadConfig

# Restrict images to common formats only
client.update_app(
    image_upload_config=FileUploadConfig(
        allowed_file_extensions=[".jpg", ".jpeg", ".png", ".gif", ".webp"],
        allowed_mime_types=["image/jpeg", "image/png", "image/gif", "image/webp"],
        size_limit=5 * 1024 * 1024,  # 5MB
    ),
    # Restrict file uploads to documents only
    file_upload_config=FileUploadConfig(
        allowed_file_extensions=[".pdf", ".doc", ".docx"],
        allowed_mime_types=["application/pdf", "application/msword"],
        size_limit=10 * 1024 * 1024,  # 10MB
    ),
)
```

</codetabs-item>

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

```go
// Restrict images to common formats only
client.UpdateApp(ctx, &getstream.UpdateAppRequest{
    ImageUploadConfig: &getstream.FileUploadConfig{
        AllowedFileExtensions: []string{".jpg", ".jpeg", ".png", ".gif", ".webp"},
        AllowedMimeTypes:      []string{"image/jpeg", "image/png", "image/gif", "image/webp"},
        SizeLimit:             5 * 1024 * 1024, // 5MB
    },
    FileUploadConfig: &getstream.FileUploadConfig{
        AllowedFileExtensions: []string{".pdf", ".doc", ".docx"},
        AllowedMimeTypes:      []string{"application/pdf", "application/msword"},
        SizeLimit:             10 * 1024 * 1024, // 10MB
    },
})
```

</codetabs-item>

</codetabs>

For more details, see [App Settings](/chat/docs/<framework>/app_setting_overview/).

### Giving moderators more permissions

Moderators have elevated permissions like the ability to ban users, delete messages, and more. You can assign moderator roles to users at the channel level or across all channels.

**Add a Moderator to a Channel:**

<codetabs>

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

```js
// Add a member with moderator role
await channel.addMembers([
  { user_id: "james_bond", channel_role: "channel_moderator" },
]);
```

</codetabs-item>

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

```python
from getstream.models import ChannelMemberRequest

# Add a member with moderator role
channel.update(add_members=[ChannelMemberRequest(user_id="james_bond", channel_role="channel_moderator")])
```

</codetabs-item>

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

```ruby
require 'getstream_ruby'
Models = GetStream::Generated::Models

# Add a member with moderator role
client.chat.update_channel('messaging', 'channel-id', Models::UpdateChannelRequest.new(
  add_members: [
    Models::ChannelMemberRequest.new(user_id: 'james_bond', channel_role: 'channel_moderator')
  ]
))
```

</codetabs-item>

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

```php
// Add a member with moderator role
$client->updateChannel('messaging', 'channel-id', new Models\UpdateChannelRequest(
    addMembers: [
        new Models\ChannelMemberRequest(
            userID: 'james_bond',
            channelRole: 'channel_moderator',
        ),
    ],
));
```

</codetabs-item>

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

```go
// Assign moderator role
channel := client.Chat().Channel("messaging", "channel-id")
_, err = channel.Update(ctx, &getstream.UpdateChannelRequest{
  AssignRoles: []getstream.ChannelMemberRequest{
    {ChannelRole: getstream.PtrTo("channel_moderator"), UserID: "james_bond"},
  },
})
```

</codetabs-item>

</codetabs>

**Make a User a Moderator Across All Channels:**

To grant a user moderator permissions across all channels in your app, update their global role:

<codetabs>

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

```js
await client.partialUpdateUser({
  id: "james_bond",
  set: { role: "admin" },
});
```

</codetabs-item>

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

```python
from getstream.models import UpdateUserPartialRequest

client.update_users_partial(users=[
    UpdateUserPartialRequest(id="james_bond", set={"role": "admin"})
])
```

</codetabs-item>

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

```php
$client->updateUsersPartial(new Models\UpdateUsersPartialRequest(
    users: [
        new Models\UpdateUserPartialRequest(
            id: 'james_bond',
            set: (object)['role' => 'admin'],
        ),
    ],
));
```

</codetabs-item>

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

```go
client.UpdateUsersPartial(ctx, &getstream.UpdateUsersPartialRequest{
  Users: []getstream.UpdateUserPartialRequest{
    {
      ID: "james_bond",
      Set: map[string]any{
        "role": "admin",
      },
    },
  },
})
```

</codetabs-item>

</codetabs>

For more details on permissions, see [User Permissions](/chat/docs/<framework>/chat_permission_policies/).

### Slow mode

Slow mode helps reduce noise on a channel by limiting users to a maximum of 1 message per cooldown interval (1-120 seconds). Moderators, admins, and server-side API calls are not restricted.

<codetabs>

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

```kotlin
val channelClient = client.channel("messaging", "general")

// Enable slow mode and set cooldown to 1s
channelClient.enableSlowMode(cooldownTimeInSeconds = 1).enqueue { /* Result handling */ }

// Increase cooldown to 30s
channelClient.enableSlowMode(cooldownTimeInSeconds = 30).enqueue { /* Result handling */ }

// Disable slow mode
channelClient.disableSlowMode().enqueue { /* Result handling */ }
```

</codetabs-item>

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

```js
// enable slow mode and set cooldown to 1s
await channel.enableSlowMode(1);

// increase cooldown to 30s
await channel.enableSlowMode(30);

// disable slow mode
await channel.disableSlowMode();
```

</codetabs-item>

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

```swift
let channelController = client.channelController(for: .init(type: .messaging, id: "general"))

channelController.enableSlowMode(cooldownDuration: 10)

channelController.disableSlowMode()
```

</codetabs-item>

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

```php
$client->updateChannelPartial('messaging', 'general', new Models\UpdateChannelPartialRequest(
    set: (object)['cooldown' => 30], // 30 sec
));
```

</codetabs-item>

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

```python
channel.update_channel_partial(set={"cooldown": 30}) # 30 sec
```

</codetabs-item>

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

```java
// Enable slow mode and set cooldown to 1s
channelClient.enableSlowMode(1).enqueue(result -> { /* Result handling */ });

// Disable slow mode
channelClient.disableSlowMode().enqueue(result -> { /* Result handling */ });
```

</codetabs-item>

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

```go
channel := client.Chat().Channel("messaging", "general")
channel.UpdateChannelPartial(ctx, &getstream.UpdateChannelPartialRequest{
  Set: map[string]any{"cooldown": 30}, // 30 sec
})
```

</codetabs-item>

</codetabs>

For more details, see [Slow Mode & Throttling](/chat/docs/<framework>/slow_mode/).

### Enforce unique usernames

This setting prevents users from using duplicate usernames. When enabled with `app`, it enforces uniqueness across the entire app. With `team`, it only enforces within the same team.

<codetabs>

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

```js
// Enable uniqueness constraints on App level
await client.updateAppSettings({
  enforce_unique_usernames: "app",
});

// Enable uniqueness constraints on Team level
await client.updateAppSettings({
  enforce_unique_usernames: "team",
});
```

</codetabs-item>

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

```python
# Enable uniqueness constraints on App level
client.update_app(enforce_unique_usernames="app")

# Enable uniqueness constraints on Team level
client.update_app(enforce_unique_usernames="team")
```

</codetabs-item>

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

```ruby
require 'getstream_ruby'
Models = GetStream::Generated::Models

# Enable uniqueness constraints on App level
client.common.update_app(Models::UpdateAppRequest.new(enforce_unique_usernames: 'app'))

# Enable uniqueness constraints on Team level
client.common.update_app(Models::UpdateAppRequest.new(enforce_unique_usernames: 'team'))
```

</codetabs-item>

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

```php
// Enable uniqueness constraints on App level
$client->updateApp(new Models\UpdateAppRequest(
    enforceUniqueUsernames: 'app',
));

// Enable uniqueness constraints on Team level
$client->updateApp(new Models\UpdateAppRequest(
    enforceUniqueUsernames: 'team',
));
```

</codetabs-item>

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

```java
// Enable uniqueness constraints on App level
client.updateApp(UpdateAppRequest.builder().enforceUniqueUsernames("app").build()).execute();

// Enable uniqueness constraints on Team level
client.updateApp(UpdateAppRequest.builder().enforceUniqueUsernames("team").build()).execute();
```

</codetabs-item>

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

```go
// Enable uniqueness constraints on App level
client.UpdateApp(ctx, &getstream.UpdateAppRequest{
  EnforceUniqueUsernames: getstream.PtrTo("app"),
})

// Enable uniqueness constraints on Team level
client.UpdateApp(ctx, &getstream.UpdateAppRequest{
  EnforceUniqueUsernames: getstream.PtrTo("team"),
})
```

</codetabs-item>

</codetabs>

### Slash commands for banning

Stream Chat supports built-in slash commands like `/ban` and `/unban` for quick moderation actions. Enable these commands on your channel type:

<codetabs>

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

```js
// Enable ban/unban commands for a channel type
await client.updateChannelType("messaging", {
  commands: ["giphy", "ban", "unban", "mute", "unmute", "flag"],
});
```

</codetabs-item>

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

```python
# Enable ban/unban commands for a channel type
client.chat.update_channel_type(
    name="messaging",
    automod="disabled",
    automod_behavior="flag",
    max_message_length=5000,
    commands=["ban", "unban", "mute", "unmute", "flag"],
)
```

</codetabs-item>

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

```ruby
require 'getstream_ruby'
Models = GetStream::Generated::Models

# Enable ban/unban commands for a channel type
client.chat.update_channel_type('messaging', Models::UpdateChannelTypeRequest.new(
  commands: ['ban', 'unban', 'mute', 'unmute', 'flag']
))
```

</codetabs-item>

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

```php
// Enable ban/unban commands for a channel type
$client->updateChannelType('messaging', new Models\UpdateChannelTypeRequest(
    automod: 'disabled',
    automodBehavior: 'flag',
    maxMessageLength: 5000,
    commands: ['giphy', 'ban', 'unban', 'mute', 'unmute', 'flag'],
));
```

</codetabs-item>

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

```java
// Enable ban/unban commands for a channel type
chat.updateChannelType("messaging", UpdateChannelTypeRequest.builder()
    .automod("disabled")
    .automodBehavior("flag")
    .maxMessageLength(5000)
    .commands(List.of("ban", "unban", "mute", "unmute", "flag"))
    .build()).execute();
```

</codetabs-item>

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

```go
// Enable ban/unban commands for a channel type
client.Chat().UpdateChannelType(ctx, "messaging", &getstream.UpdateChannelTypeRequest{
  Automod:          "disabled",
  AutomodBehavior:  "flag",
  MaxMessageLength: 5000,
  Commands:         []string{"ban", "unban", "mute", "unmute", "flag"},
})
```

</codetabs-item>

</codetabs>

## Simple Moderation features

### Blocklist

A Blocklist is a list of words that you can use to moderate chat messages. Stream Chat comes with a built-in Blocklist called `profanity_en_2020_v1` which contains over a thousand of the most common profane words.

You can manage your own blocklists via the Stream dashboard or APIs to a manage blocklists and configure your channel types to use them. Channel types can be configured to block or flag messages from your users based on your blocklists. To do this you need to configure your channel type(s) with these two configurations: `blocklist` and `blocklist_behavior` . The first one refers to the name of the blocklist and the second must be set as `block` or `flag` .

- Applications can have up to 15 blocklists in total alongside advanced filters

- A Blocklist can contain up to 10,000 words, each word can be up to 40 characters

- The blocklist words must be in lowercase

- Text matching is done with case insensitive word match (no prefix, post-fix support)

- Messages are split into words using white spaces and hyphens (cookie-monster matches both "cookie" and "monster")

So for instance, if you have a blocklist with the word "cream" these messages will be blocked or flagged:

- She jabbed the spoon in the ice cream and sighed

- Cream is the best

and it will not affect any of these

- Is creamcheese a word?

- I did not enjoy watching Scream

<admonition type="info">

The default blocklist contains material that many will find offensive.

</admonition>

#### Setup example

Blocklists can be managed using the APIs like any other Chat feature. Here is a simple example on how to create a Blocklist and use it for a channel type.

<codetabs>

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

```js
// add a new blocklist for this app
await client.createBlockList({
  name: "no-cakes",
  words: ["fudge", "cream", "sugar"],
});

// use the blocklist for all channels of type messaging
await client.updateChannelType("messaging", {
  blocklist: "no-cakes",
  blocklist_behavior: "block",
});
```

</codetabs-item>

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

```php
// add a new blocklist for this app
$client->createBlockList(new Models\CreateBlockListRequest(
    name: 'no-cakes',
    words: ['fudge', 'cream', 'sugar'],
));

// use the blocklist for all channels of type messaging
$client->updateChannelType('messaging', new Models\UpdateChannelTypeRequest(
    automod: 'disabled',
    automodBehavior: 'flag',
    maxMessageLength: 5000,
    blocklist: 'no-cakes',
    blocklistBehavior: 'block',
));
```

</codetabs-item>

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

```python
# add a new blocklist for this app
client.create_block_list(name="no-cakes", words=["fudge", "cream", "sugar"])

# use the blocklist for all channels of type messaging
client.chat.update_channel_type(
    name="messaging",
    automod="disabled",
    automod_behavior="flag",
    max_message_length=5000,
    blocklist="no-cakes",
    blocklist_behavior="block",
)
```

</codetabs-item>

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

```ruby
require 'getstream_ruby'
Models = GetStream::Generated::Models

# add a new blocklist for this app
client.common.create_block_list(Models::CreateBlockListRequest.new(
  name: 'no-cakes',
  words: ['fudge', 'cream', 'sugar']
))

# use the blocklist for all channels of type messaging
client.chat.update_channel_type('messaging', Models::UpdateChannelTypeRequest.new(
  blocklist: 'no-cakes',
  blocklist_behavior: 'block'
))
```

</codetabs-item>

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

```java
// add a new blocklist for this app
client.createBlockList(CreateBlockListRequest.builder()
    .name("no-cakes")
    .words(List.of("fudge", "cream", "sugar"))
    .build()).execute();

// use the blocklist for all channels of type messaging
chat.updateChannelType("messaging", UpdateChannelTypeRequest.builder()
    .automod("disabled")
    .automodBehavior("flag")
    .maxMessageLength(5000)
    .blocklist("no-cakes")
    .blocklistBehavior("block")
    .build()).execute();
```

</codetabs-item>

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

```go
// add a new blocklist for this app
client.CreateBlockList(ctx, &getstream.CreateBlockListRequest{
  Name:  "no-cakes",
  Words: []string{"fudge", "cream", "sugar"},
})

// use the blocklist for all channels of type messaging
client.Chat().UpdateChannelType(ctx, "messaging", &getstream.UpdateChannelTypeRequest{
  Automod:           "disabled",
  AutomodBehavior:   "flag",
  MaxMessageLength:  5000,
  Blocklist:         getstream.PtrTo("no-cakes"),
  BlocklistBehavior: getstream.PtrTo("block"),
})
```

</codetabs-item>

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

```csharp
// add a new blocklist for this app
await client.CreateBlockListAsync(new CreateBlockListRequest
{
    Name = "no-cakes",
    Words = new List<string> { "fudge", "cream", "sugar" },
});

// use the blocklist for all channels of type messaging
```

</codetabs-item>

</codetabs>

#### List available blocklists

All applications have the `profanity_en_2020_v1` blocklist available. This endpoint returns all blocklists available for this application.

<codetabs>

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

```js
await client.listBlockLists();
```

</codetabs-item>

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

```php
$response = $client->listBlockLists('');
```

</codetabs-item>

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

```python
client.list_block_lists()
```

</codetabs-item>

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

```ruby
require 'getstream_ruby'

client.common.list_block_lists
```

</codetabs-item>

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

```java
client.listBlockLists(ListBlockListsRequest.builder().build()).execute();
```

</codetabs-item>

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

```go
client.ListBlockLists(ctx, &getstream.ListBlockListsRequest{})
```

</codetabs-item>

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

```csharp
await client.ListBlockListsAsync();
```

</codetabs-item>

</codetabs>

#### Describe a blocklist

<codetabs>

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

```js
await client.getBlockList("no-cakes");
```

</codetabs-item>

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

```php
$response = $client->getBlockList('no-cakes', '');
```

</codetabs-item>

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

```python
client.get_block_list("no-cakes")
```

</codetabs-item>

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

```ruby
require 'getstream_ruby'

client.common.get_block_list('no-cakes')
```

</codetabs-item>

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

```java
client.getBlockList("no-cakes", GetBlockListRequest.builder().build()).execute();
```

</codetabs-item>

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

```go
client.GetBlockList(ctx, "no-cakes", &getstream.GetBlockListRequest{})
```

</codetabs-item>

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

```csharp
await client.GetBlockListAsync("no-cakes");
```

</codetabs-item>

</codetabs>

#### Create new blocklist

<codetabs>

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

```js
const words = ["fudge", "cream", "sugar"];
await client.createBlockList({
  name: "no-cakes",
  words,
});
```

</codetabs-item>

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

```php
$client->createBlockList(new Models\CreateBlockListRequest(
    name: 'no-cakes',
    words: ['fudge', 'cream', 'sugar'],
));
```

</codetabs-item>

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

```python
client.create_block_list(name="no-cakes", words=["fudge", "cream", "sugar"])
```

</codetabs-item>

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

```ruby
require 'getstream_ruby'
Models = GetStream::Generated::Models

client.common.create_block_list(Models::CreateBlockListRequest.new(
  name: 'no-cakes',
  words: ['fudge', 'cream', 'sugar']
))
```

</codetabs-item>

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

```java
client.createBlockList(CreateBlockListRequest.builder()
    .name("no-cakes")
    .words(List.of("fudge", "cream", "sugar"))
    .build()).execute();
```

</codetabs-item>

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

```go
client.CreateBlockList(ctx, &getstream.CreateBlockListRequest{
  Name:  "no-cakes",
  Words: []string{"fudge", "cream", "sugar"},
})
```

</codetabs-item>

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

```csharp
await client.CreateBlockListAsync(new CreateBlockListRequest
{
    Name = "no-cakes",
    Words = new List<string> { "fudge", "cream", "sugar" },
});
```

</codetabs-item>

</codetabs>

#### Update a blocklist

<codetabs>

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

```js
await client.updateBlockList("no-cakes", {
  words: ["fudge", "cream", "sugar", "vanilla"],
});
```

</codetabs-item>

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

```php
$client->updateBlockList('no-cakes', new Models\UpdateBlockListRequest(
    words: ['fudge', 'cream', 'sugar', 'vanilla'],
));
```

</codetabs-item>

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

```python
client.update_block_list("no-cakes", words=["fudge", "cream", "sugar", "vanilla"])
```

</codetabs-item>

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

```ruby
require 'getstream_ruby'
Models = GetStream::Generated::Models

client.common.update_block_list('no-cakes', Models::UpdateBlockListRequest.new(
  words: ['fudge', 'cream', 'sugar', 'vanilla']
))
```

</codetabs-item>

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

```java
client.updateBlockList("no-cakes", UpdateBlockListRequest.builder()
    .words(List.of("fudge", "cream", "sugar", "vanilla"))
    .build()).execute();
```

</codetabs-item>

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

```go
client.UpdateBlockList(ctx, "no-cakes", &getstream.UpdateBlockListRequest{
  Words: []string{"fudge", "cream", "sugar", "vanilla"},
})
```

</codetabs-item>

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

```csharp
await client.UpdateBlockListAsync("no-cakes", new UpdateBlockListRequest
{
    Words = new List<string> { "fudge", "cream", "sugar", "vanilla" },
});
```

</codetabs-item>

</codetabs>

#### Delete a blocklist

When a blocklist is deleted, it will be automatically removed from all channel types that were using it.

<codetabs>

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

```js
await client.deleteBlockList("no-cakes");
```

</codetabs-item>

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

```php
$client->deleteBlockList('no-cakes', '');
```

</codetabs-item>

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

```python
client.delete_block_list("no-cakes")
```

</codetabs-item>

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

```ruby
require 'getstream_ruby'

client.common.delete_block_list('no-cakes')
```

</codetabs-item>

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

```java
client.deleteBlockList("no-cakes", DeleteBlockListRequest.builder().build()).execute();
```

</codetabs-item>

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

```go
client.DeleteBlockList(ctx, "no-cakes", &getstream.DeleteBlockListRequest{})
```

</codetabs-item>

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

```csharp
await client.DeleteBlockListAsync("no-cakes");
```

</codetabs-item>

</codetabs>

### Regex

Regex filters allow you to match and moderate messages using regular expressions. This is useful for filtering patterns like phone numbers, URLs, or complex word variations. Configure regex filters via the Stream dashboard under 'Blocklist & Regex Filters'.

For detailed configuration, see [Regex, Email, and Domain Filters](/moderation/docs/engines/blocklists-and-regex-filters/).

### Email/domain allow or block

You can configure domain and email filters to control what URLs and email addresses can be shared in messages. Set up allowlists or blocklists for specific domains via the Stream dashboard.

For detailed configuration, see [Regex, Email, and Domain Filters](/moderation/docs/engines/blocklists-and-regex-filters/).

## User Actions

### Flag

Any user can flag a message or user. Flagged content is added to your moderation review queue on the 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) {
    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) {
    val flag: Flag = result.data()
  } else {
    // Handle result.error()
  }
}
```

</codetabs-item>

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

```js
// Flag a message
const flag = await client.flagMessage(messageId);

// Flag with a reason and custom data
const flag = await client.flagMessage(messageId, {
  reason: "spammy_user",
  custom: {
    user_comment: "This user is spamming.",
  },
});
```

</codetabs-item>

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

```swift
import StreamChat

let messageController = chatClient.messageController(cid: channelId, messageId: messageId)

messageController.flag { error in
  print(error ?? "message flagged")
}

// Flag a user
let userController = chatClient.userController(userId: "another_user")
userController.flag { error in
  print(error ?? "user flagged")
}
```

</codetabs-item>

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

```python
client.moderation.flag(
    entity_type="stream:chat:v1:message",
    entity_id=msg["id"],
    user_id=server_user["id"],
)
```

</codetabs-item>

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

```ruby
require 'getstream_ruby'
Models = GetStream::Generated::Models

client.moderation.flag(Models::FlagRequest.new(
  entity_type: 'stream:chat:v1:message',
  entity_id: msg_id,
  user_id: server_user[:id]
))
```

</codetabs-item>

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

```go
client.Moderation().Flag(ctx, &getstream.FlagRequest{
  EntityType:      "message",
  EntityID:        msg.ID,
  EntityCreatorID: getstream.PtrTo(msg.UserID),
  UserID:          getstream.PtrTo(user.ID),
})
```

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

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

### Mute

Users can mute other users. Mutes are stored at the user level and returned when `connectUser` is called. Messages from muted users are still delivered via websocket but not via push notifications.

See [Mute in the Moderation API](/moderation/docs/api/flag-mute-ban/#mute) for full documentation and SDK examples.

### Block

The user block feature allows users to control their 1-on-1 interactions within the chat application by blocking other users.

```js
await client.blockUser("user-to-block");
```

#### How blocking impacts chat

When a user is blocked, several changes occur:

- **Direct Communication Termination**: When a user blocks another user, communication in all 1-on-1 channels are hidden for the blocking user.
- **Adding to Channels**: If a blocked user tries to add the blocking user to a channel as a member, the action is ignored. The channel will not include the blocking user but will have the remaining members.
- **Push Notifications**: The blocking user will not receive push notifications from blocked users for 1-on-1 channels.
- **Channel Events**: The blocking user will not receive any events from blocked users in 1-on-1 channels (e.g., message.new).
- **Group Channels**: Group channels are unaffected by the block. Both the blocking and blocked users can participate, receive push notifications, and events in group channels.
- **Query Channels**: When hidden channels are requested, 1-on-1 channels with blocked users will be returned with a `blocked:true` flag and all the messages.
- **Active Chats and Unread Counts**: Blocked users will not appear in the blocking user's list of active chats. Messages from blocked users will not contribute to unread counts.
- **Unblocking Users**: After unblocking, all previous messages in 1-on-1 channels become visible again, including those sent during the block period.
- **Hidden Channels**: Channels with only the blocked and blocking users are marked as hidden for the blocking user by default. If a blocked user sends a message in a hidden channel, the channel remains hidden for the blocking user.
- **Group Channel Messages**: Messages from blocked users will still appear when retrieving messages from a group channel.
- **WebSocket Connection**: When connecting to the WebSocket, the blocking user receives a list of users they have blocked (user.blocked_users). This is only available for the blocking user's own account.
- **Message Actions**: Actions such as sending, updating, reacting to, and deleting messages will still work in blocked channels. However, since the channels are hidden, these actions will not be visible to the blocking user.

#### Block User

Any user is allowed to block another user. Blocked users are stored at the user level and returned with the rest of the user information when connectUser is called. A user will be blocked until the user is unblocked.

<codetabs>

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

```js
await client.blockUser("user-to-block");
```

</codetabs-item>

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

```kotlin
client.blockUser("user-to-block-id").enqueue { /* ... */ }
```

</codetabs-item>

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

```swift
chatClient.userController(userId: userToBlock.id).block { error in ... }
```

</codetabs-item>

</codetabs>

#### Unblock user

<codetabs>

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

```js
await client.unBlockUser(blockedUser);
```

</codetabs-item>

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

```kotlin
client.unblockUser("user-to-unblock-id").enqueue { /* ... */ }
```

</codetabs-item>

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

```swift
chatClient.userController(userId: userToUnblock.id).unblock { error in ... }
```

</codetabs-item>

</codetabs>

#### List of Blocked Users

<codetabs>

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

```js
const resp = await client.getBlockedUsers();
```

</codetabs-item>

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

```kotlin
client.queryBlockedUsers().enqueue { result ->
    if (result is Result.Success) {
        val blockedUsers: List<UserBlock> = result.value
    } else {
        // Handle Result.Failure
    }
}
```

</codetabs-item>

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

```swift
chatClient.currentUserController().loadBlockedUsers { result in
  switch result {
  case .success(let blockedUsers):
    // Handle success
  case .failure(let error):
    // Handle error
  }
}
```

</codetabs-item>

</codetabs>

#### Server Side

**Block User:**

<codetabs>

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

```js
const blockingUser = "user1";
const blockedUser = "user2";
await ctx.createUsers([blockingUser, blockedUser]);
const serverClient = await ctx.serverClient();

await serverClient.blockUser(blockedUser, blockingUser);
const resp = await serverClient.getBlockedUsers(blockingUser);
```

</codetabs-item>

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

```go
resp, err := client.BlockUsers(ctx, &getstream.BlockUsersRequest{
  BlockedUserID: blockedUserID,
  UserID:        getstream.PtrTo(blockingUserID),
})
```

</codetabs-item>

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

```csharp
await client.BlockUsersAsync(new BlockUsersRequest
{
    BlockedUserID = user2.Id,
    UserID = user1.Id,
});
```

</codetabs-item>

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

```java
var blockResponse = client.blockUsers(BlockUsersRequest.builder()
    .blockedUserID(blockedUserId)
    .userID(blockingUserId)
    .build()).execute();
```

</codetabs-item>

</codetabs>

**Unblock user:**

<codetabs>

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

```js
await serverClient.unBlockUser(blockedUser, blockingUser);
```

</codetabs-item>

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

```go
resp, err := client.UnblockUsers(ctx, &getstream.UnblockUsersRequest{
  BlockedUserID: blockedUserID,
  UserID:        getstream.PtrTo(blockingUserID),
})
```

</codetabs-item>

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

```csharp
await client.UnblockUsersAsync(new UnblockUsersRequest
{
    BlockedUserID = user2.Id,
    UserID = user1.Id,
});
```

</codetabs-item>

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

```java
var unblockResponse = client.unblockUsers(UnblockUsersRequest.builder()
    .blockedUserID(blockedUserId)
    .userID(blockingUserId)
    .build()).execute();
```

</codetabs-item>

</codetabs>

**Get List of Blocked Users:**

<codetabs>

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

```js
const resp = await client.getBlockedUsers(blockingUser);
```

</codetabs-item>

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

```go
getRes, err := client.GetBlockedUsers(ctx, &getstream.GetBlockedUsersRequest{
  UserID: getstream.PtrTo(blockingUserID),
})
for _, blockedUser := range getRes.Data.Blocks {
  fmt.Println(blockedUser.BlockedUserID)
}
```

</codetabs-item>

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

```csharp
await client.GetBlockedUsersAsync(user1.ID);
```

</codetabs-item>

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

```java
var getBlockedUsersResponse = client.getBlockedUsers(GetBlockedUsersRequest.builder()
    .UserID(blockingUserId)
    .build()).execute();
```

</codetabs-item>

</codetabs>

## AI moderation

AI moderation can detect over 40 harms in 50+ different languages.
In addition to these classification models LLM based moderation is also supported.
Moderation APIs are available at additional costs.
It's priced to be cost-effective and typically is a fraction of the cost of other moderation APIs.

[Read the full AI moderation docs](/moderation/docs/).

## Ban

Users can be banned from an app entirely or from a channel. When banned, they cannot post messages until the ban is removed or expires. You can also apply IP bans and optionally delete the user's messages.

See [Ban in the Moderation API](/moderation/docs/api/flag-mute-ban/#ban) for full documentation, including shadow bans, query endpoints, and SDK examples.


---

This page was last updated at 2026-03-13T13:15:51.866Z.

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