# Multitenancy

Many applications with chat functionality serve multiple customers or organizations. For example, platforms like Slack or SaaS applications like InVision need to ensure that messages remain private between different customer organizations. Stream Chat addresses this through multi-tenant mode, which organizes users into separate teams that operate independently. For more details on multitenancy setup, see our [Multitenancy](/chat/docs/javascript/multi_tenant_chat/) documentation.

This guide explains how you can provide moderation experience for your customers (tenants) by using team-level moderation policies. This will allow you to build a multitenant dashboard for your customers to manage their moderation policies and also allow them to review flagged content for their team. Basically we are providing you the APIs that Stream users to build the dashboard for their customers and you can use these APIs to build the dashboard for your customers however you want.

## Limitations and Prerequisites

- Multi-tenancy is only supported on chat.
- Team level semantic phrase lists are not supported.

## Create Moderation Policy

You can create moderation configs at the team level by setting the `team` field in the [`UpsertConfig`](/moderation/docs/<framework>/configuration/policies/#upsert-config) API.
Following moderation configuration will be used for all the messages sent to `messaging` channel type within the team `team-a`.

<admonition type="warning">
Only team admins and moderators can manage team-level policies
</admonition>

### Server Side Integration

The server-side integration provides full control over moderation configs and is typically used for:

- Initial setup of team moderation policies
- Bulk updates to moderation rules
- Administrative operations

<codetabs>

<codetabs-item value="js" label="Node">

```js
await client.moderation.upsertConfig({
  key: "chat:messaging",
  team: "team-a",
  ai_text_config: {
    rules: [{ label: "SEXUAL_HARASSMENT", action: "flag" }],
  },
  ai_image_config: {
    rules: [{ label: "Non-Explicit Nudity", action: "flag" }],
  },
});
```

</codetabs-item>

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

```py
client.moderation().upsert_config(
    key="chat:messaging",
    team="team-a",
    ai_text_config={
        "rules": [{"label": "SEXUAL_HARASSMENT", "action": "flag"}],
    },
    ai_image_config={
        "rules": [{"label": "Non-Explicit Nudity", "action": "flag"}],
    },
)
```

</codetabs-item>

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

```go
client.Moderation().UpsertConfig(ctx, &getstream.UpsertConfigRequest{
    Key:  "chat:messaging",
    Team: getstream.PtrTo("team-a"),
    AiTextConfig: &getstream.AiTextConfig{
        Rules: []getstream.AiTextRule{
            {Label: "SEXUAL_HARASSMENT", Action: "flag"},
        },
    },
    AiImageConfig: &getstream.AiImageConfig{
        Rules: []getstream.AiImageRule{
            {Label: "Non-Explicit Nudity", Action: "flag"},
        },
    },
})
```

</codetabs-item>

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

```java
client.moderation().upsertConfig(UpsertConfigRequest.builder()
    .key("chat:messaging")
    .team("team-a")
    .aiTextConfig(AiTextConfig.builder()
        .rules(List.of(AiTextRule.builder().label("SEXUAL_HARASSMENT").action("flag").build()))
        .build())
    .aiImageConfig(AiImageConfig.builder()
        .rules(List.of(AiImageRule.builder().label("Non-Explicit Nudity").action("flag").build()))
        .build())
    .build())
    .execute();
```

</codetabs-item>

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

```php
$client->moderation()->upsertConfig(new UpsertConfigRequest(
    key: 'chat:messaging',
    team: 'team-a',
    aiTextConfig: new AiTextConfig(
        rules: [new AiTextRule(label: 'SEXUAL_HARASSMENT', action: 'flag')],
    ),
    aiImageConfig: new AiImageConfig(
        rules: [new AiImageRule(label: 'Non-Explicit Nudity', action: 'flag')],
    ),
));
```

</codetabs-item>

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

```ruby
client.moderation.upsert_config(GetStream::Generated::Models::UpsertConfigRequest.new(
  key: "chat:messaging",
  team: "team-a",
  ai_text_config: GetStream::Generated::Models::AiTextConfig.new(
    rules: [GetStream::Generated::Models::AiTextRule.new(label: "SEXUAL_HARASSMENT", action: "flag")],
  ),
  ai_image_config: GetStream::Generated::Models::AiImageConfig.new(
    rules: [GetStream::Generated::Models::AiImageRule.new(label: "Non-Explicit Nudity", action: "flag")],
  ),
))
```

</codetabs-item>

<codetabs-item value="dotnet" label=".NET">

```csharp
await client.Moderation.UpsertConfigAsync(new UpsertConfigRequest
{
    Key = "chat:messaging",
    Team = "team-a",
    AiTextConfig = new AiTextConfig
    {
        Rules = new List<AiTextRule>
        {
            new AiTextRule { Label = "SEXUAL_HARASSMENT", Action = "flag" },
        },
    },
    AiImageConfig = new AiImageConfig
    {
        Rules = new List<AiImageRule>
        {
            new AiImageRule { Label = "Non-Explicit Nudity", Action = "flag" },
        },
    },
});
```

</codetabs-item>

</codetabs>

### Client Side Integration

Client side access to API allows you to give control to your customers (tenants) to manage their own moderation policies. This is particularly useful when:

- Building a self-service moderation dashboard
- Allowing teams to customize their moderation settings
- Providing real-time moderation management

<codetabs>

<codetabs-item value="js" label="Node">

```js
const client = new StreamChat("api_key");
await client.connectUser({ id: "user-from-team-a" }, token);

await client.moderation.upsertConfig({
  key: "chat:messaging",
  team: "team-a",
  ai_text_config: {
    rules: [{ label: "SEXUAL_HARASSMENT", action: "flag" }],
  },
  ai_image_config: {
    rules: [{ label: "Non-Explicit Nudity", action: "flag" }],
  },
});
```

</codetabs-item>

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

```py
client.moderation().upsert_config(
    key="chat:messaging",
    team="team-a",
    ai_text_config={
        "rules": [{"label": "SEXUAL_HARASSMENT", "action": "flag"}],
    },
    ai_image_config={
        "rules": [{"label": "Non-Explicit Nudity", "action": "flag"}],
    },
)
```

</codetabs-item>

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

```go
client.Moderation().UpsertConfig(ctx, &getstream.UpsertConfigRequest{
    Key:  "chat:messaging",
    Team: getstream.PtrTo("team-a"),
    AiTextConfig: &getstream.AiTextConfig{
        Rules: []getstream.AiTextRule{
            {Label: "SEXUAL_HARASSMENT", Action: "flag"},
        },
    },
    AiImageConfig: &getstream.AiImageConfig{
        Rules: []getstream.AiImageRule{
            {Label: "Non-Explicit Nudity", Action: "flag"},
        },
    },
})
```

</codetabs-item>

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

```java
client.moderation().upsertConfig(UpsertConfigRequest.builder()
    .key("chat:messaging")
    .team("team-a")
    .aiTextConfig(AiTextConfig.builder()
        .rules(List.of(AiTextRule.builder().label("SEXUAL_HARASSMENT").action("flag").build()))
        .build())
    .aiImageConfig(AiImageConfig.builder()
        .rules(List.of(AiImageRule.builder().label("Non-Explicit Nudity").action("flag").build()))
        .build())
    .build())
    .execute();
```

</codetabs-item>

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

```php
$client->moderation()->upsertConfig(new UpsertConfigRequest(
    key: 'chat:messaging',
    team: 'team-a',
    aiTextConfig: new AiTextConfig(
        rules: [new AiTextRule(label: 'SEXUAL_HARASSMENT', action: 'flag')],
    ),
    aiImageConfig: new AiImageConfig(
        rules: [new AiImageRule(label: 'Non-Explicit Nudity', action: 'flag')],
    ),
));
```

</codetabs-item>

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

```ruby
client.moderation.upsert_config(GetStream::Generated::Models::UpsertConfigRequest.new(
  key: "chat:messaging",
  team: "team-a",
  ai_text_config: GetStream::Generated::Models::AiTextConfig.new(
    rules: [GetStream::Generated::Models::AiTextRule.new(label: "SEXUAL_HARASSMENT", action: "flag")],
  ),
  ai_image_config: GetStream::Generated::Models::AiImageConfig.new(
    rules: [GetStream::Generated::Models::AiImageRule.new(label: "Non-Explicit Nudity", action: "flag")],
  ),
))
```

</codetabs-item>

<codetabs-item value="dotnet" label=".NET">

```csharp
await client.Moderation.UpsertConfigAsync(new UpsertConfigRequest
{
    Key = "chat:messaging",
    Team = "team-a",
    AiTextConfig = new AiTextConfig
    {
        Rules = new List<AiTextRule>
        {
            new AiTextRule { Label = "SEXUAL_HARASSMENT", Action = "flag" },
        },
    },
    AiImageConfig = new AiImageConfig
    {
        Rules = new List<AiImageRule>
        {
            new AiImageRule { Label = "Non-Explicit Nudity", Action = "flag" },
        },
    },
});
```

</codetabs-item>

</codetabs>

<admonition type="note">

Please check [AI Text Supported Harm Categories](/moderation/docs/<framework>/engines/ai-text/#supported-harms-and-categories) and [AI Image Supported Harm Categories](/moderation/docs/<framework>/engines/image-moderation/#supported-harms-and-categories) for the list of supported harm categories.

</admonition>

## Moderation Policy Scopes

The scope of moderation policies is determined by the combination of the config key and team field. Here's a detailed breakdown:

| Config Key                    | Team Field | Scope                                                        | Use Case                            |
| ----------------------------- | ---------- | ------------------------------------------------------------ | ----------------------------------- |
| `chat`                        |            | Applies to all channels across all teams                     | Global moderation policies          |
| `chat`                        | `team-a`   | Applies to all channels in team-a                            | Team-wide policies                  |
| `chat:messaging`              |            | Applies to messaging channels across all teams               | Channel-type specific policies      |
| `chat:messaging`              | `team-a`   | Applies to messaging channels in team-a                      | Team-specific channel type policies |
| `chat:messaging:<channel_id>` |            | Applies to channel with id `channel_id` irrespective of team | Channel-specific policies           |
| `chat:messaging:<channel_id>` | `team-a`   | Applies to channel with id `channel_id` in team-a            | Team-specific channel policies      |

## Query Moderation Policies

You can query moderation configs at the team level to:

- Audit existing moderation policies
- Review team-specific settings
- Monitor policy changes

### Using Server side integration

The server-side query provides comprehensive access to all moderation configs:

<codetabs>

<codetabs-item value="js" label="Node">

```js
// Get all the moderation configs for all the teams
const { configs, next } = await client.moderation.queryModerationConfigs({
  limit: 2,
});

// Get all the moderation configs for the team `team-a`
const { configs, next } = await client.moderation.queryModerationConfigs({
  filter: { team: "team-a" },
  sort: [{ field: "created_at", direction: -1 }],
  limit: 2,
});
```

</codetabs-item>

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

```py
# Get all the moderation configs for all the teams
client.moderation().query_moderation_configs(limit=2)

# Get all the moderation configs for the team `team-a`
client.moderation().query_moderation_configs(
    filter={"team": "team-a"},
    sort=[{"field": "created_at", "direction": -1}],
    limit=2,
)
```

</codetabs-item>

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

```go
// Get all the moderation configs for all the teams
client.Moderation().QueryModerationConfigs(ctx, &getstream.QueryModerationConfigsRequest{
    Limit: getstream.PtrTo(2),
})

// Get all the moderation configs for the team `team-a`
client.Moderation().QueryModerationConfigs(ctx, &getstream.QueryModerationConfigsRequest{
    Filter: map[string]interface{}{"team": "team-a"},
    Sort:   []getstream.SortParam{{Field: "created_at", Direction: getstream.PtrTo(-1)}},
    Limit:  getstream.PtrTo(2),
})
```

</codetabs-item>

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

```java
// Get all the moderation configs for all the teams
client.moderation().queryModerationConfigs(QueryModerationConfigsRequest.builder()
    .limit(2)
    .build())
    .execute();

// Get all the moderation configs for the team `team-a`
client.moderation().queryModerationConfigs(QueryModerationConfigsRequest.builder()
    .filter(Map.of("team", "team-a"))
    .sort(List.of(SortParam.builder().field("created_at").direction(-1).build()))
    .limit(2)
    .build())
    .execute();
```

</codetabs-item>

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

```php
// Get all the moderation configs for all the teams
$client->moderation()->queryModerationConfigs(new QueryModerationConfigsRequest(
    limit: 2,
));

// Get all the moderation configs for the team `team-a`
$client->moderation()->queryModerationConfigs(new QueryModerationConfigsRequest(
    filter: ['team' => 'team-a'],
    sort: [new SortParam(field: 'created_at', direction: -1)],
    limit: 2,
));
```

</codetabs-item>

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

```ruby
# Get all the moderation configs for all the teams
client.moderation.query_moderation_configs(GetStream::Generated::Models::QueryModerationConfigsRequest.new(
  limit: 2,
))

# Get all the moderation configs for the team `team-a`
client.moderation.query_moderation_configs(GetStream::Generated::Models::QueryModerationConfigsRequest.new(
  filter: { "team" => "team-a" },
  sort: [GetStream::Generated::Models::SortParam.new(field: "created_at", direction: -1)],
  limit: 2,
))
```

</codetabs-item>

<codetabs-item value="dotnet" label=".NET">

```csharp
// Get all the moderation configs for all the teams
await client.Moderation.QueryModerationConfigsAsync(new QueryModerationConfigsRequest
{
    Limit = 2,
});

// Get all the moderation configs for the team `team-a`
await client.Moderation.QueryModerationConfigsAsync(new QueryModerationConfigsRequest
{
    Filter = new Dictionary<string, object> { { "team", "team-a" } },
    Sort = new List<SortParam>
    {
        new SortParam { Field = "created_at", Direction = -1 },
    },
    Limit = 2,
});
```

</codetabs-item>

</codetabs>

### Using Client side integration

Client-side queries are restricted to the team context of the authenticated user:

<codetabs>

<codetabs-item value="js" label="Node">

```js
// Get all the moderation configs for the team that the user belongs to
const { configs, next } = await client.moderation.queryModerationConfigs({
  limit: 2,
});
```

</codetabs-item>

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

```py
# Get all the moderation configs for the team that the user belongs to
client.moderation().query_moderation_configs(limit=2)
```

</codetabs-item>

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

```go
// Get all the moderation configs for the team that the user belongs to
client.Moderation().QueryModerationConfigs(ctx, &getstream.QueryModerationConfigsRequest{
    Limit: getstream.PtrTo(2),
})
```

</codetabs-item>

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

```java
// Get all the moderation configs for the team that the user belongs to
client.moderation().queryModerationConfigs(QueryModerationConfigsRequest.builder()
    .limit(2)
    .build())
    .execute();
```

</codetabs-item>

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

```php
// Get all the moderation configs for the team that the user belongs to
$client->moderation()->queryModerationConfigs(new QueryModerationConfigsRequest(
    limit: 2,
));
```

</codetabs-item>

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

```ruby
# Get all the moderation configs for the team that the user belongs to
client.moderation.query_moderation_configs(GetStream::Generated::Models::QueryModerationConfigsRequest.new(
  limit: 2,
))
```

</codetabs-item>

<codetabs-item value="dotnet" label=".NET">

```csharp
// Get all the moderation configs for the team that the user belongs to
await client.Moderation.QueryModerationConfigsAsync(new QueryModerationConfigsRequest
{
    Limit = 2,
});
```

</codetabs-item>

</codetabs>

## Query Review Queue

The review queue is a crucial component of the moderation system that allows teams to:

- Review flagged content
- Take action on moderated messages
- Track moderation history
- Maintain audit trails

You can query the review queue at the team level to using [`QueryReviewQueue`](/moderation/docs/<framework>/content-moderation/review-queue/#query-review-queue) API.

### Server Side Integration

Server-side access provides complete control over the review queue:

<codetabs>

<codetabs-item value="js" label="Node">

```js
// Get all the review queue for all the teams
const { reviews, next } = await client.moderation.queryReviewQueue({
  limit: 2,
});

// Get all the review queue for the team `team-a`
const { reviews, next } = await client.moderation.queryReviewQueue({
  filter: { teams: "team-a" },
  sort: [{ field: "created_at", direction: -1 }],
  limit: 2,
});

// Get all the review queue for the team `team-a` and `team-b`
const { reviews, next } = await client.moderation.queryReviewQueue({
  filter: { teams: { $in: ["team-a", "team-b"] } },
  sort: [{ field: "created_at", direction: -1 }],
  limit: 2,
});
```

</codetabs-item>

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

```py
# Get all the review queue for all the teams
client.moderation().query_review_queue(limit=2)

# Get all the review queue for the team `team-a`
client.moderation().query_review_queue(
    filter={"teams": "team-a"},
    sort=[{"field": "created_at", "direction": -1}],
    limit=2,
)

# Get all the review queue for the team `team-a` and `team-b`
client.moderation().query_review_queue(
    filter={"teams": {"$in": ["team-a", "team-b"]}},
    sort=[{"field": "created_at", "direction": -1}],
    limit=2,
)
```

</codetabs-item>

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

```go
// Get all the review queue for all the teams
client.Moderation().QueryReviewQueue(ctx, &getstream.QueryReviewQueueRequest{
    Limit: getstream.PtrTo(2),
})

// Get all the review queue for the team `team-a`
client.Moderation().QueryReviewQueue(ctx, &getstream.QueryReviewQueueRequest{
    Filter: map[string]interface{}{"teams": "team-a"},
    Sort:   []getstream.SortParam{{Field: "created_at", Direction: getstream.PtrTo(-1)}},
    Limit:  getstream.PtrTo(2),
})

// Get all the review queue for the team `team-a` and `team-b`
client.Moderation().QueryReviewQueue(ctx, &getstream.QueryReviewQueueRequest{
    Filter: map[string]interface{}{"teams": map[string]interface{}{"$in": []string{"team-a", "team-b"}}},
    Sort:   []getstream.SortParam{{Field: "created_at", Direction: getstream.PtrTo(-1)}},
    Limit:  getstream.PtrTo(2),
})
```

</codetabs-item>

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

```java
// Get all the review queue for all the teams
client.moderation().queryReviewQueue(QueryReviewQueueRequest.builder()
    .limit(2)
    .build())
    .execute();

// Get all the review queue for the team `team-a`
client.moderation().queryReviewQueue(QueryReviewQueueRequest.builder()
    .filter(Map.of("teams", "team-a"))
    .sort(List.of(SortParam.builder().field("created_at").direction(-1).build()))
    .limit(2)
    .build())
    .execute();

// Get all the review queue for the team `team-a` and `team-b`
client.moderation().queryReviewQueue(QueryReviewQueueRequest.builder()
    .filter(Map.of("teams", Map.of("$in", List.of("team-a", "team-b"))))
    .sort(List.of(SortParam.builder().field("created_at").direction(-1).build()))
    .limit(2)
    .build())
    .execute();
```

</codetabs-item>

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

```php
// Get all the review queue for all the teams
$client->moderation()->queryReviewQueue(new QueryReviewQueueRequest(
    limit: 2,
));

// Get all the review queue for the team `team-a`
$client->moderation()->queryReviewQueue(new QueryReviewQueueRequest(
    filter: ['teams' => 'team-a'],
    sort: [new SortParam(field: 'created_at', direction: -1)],
    limit: 2,
));

// Get all the review queue for the team `team-a` and `team-b`
$client->moderation()->queryReviewQueue(new QueryReviewQueueRequest(
    filter: ['teams' => ['$in' => ['team-a', 'team-b']]],
    sort: [new SortParam(field: 'created_at', direction: -1)],
    limit: 2,
));
```

</codetabs-item>

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

```ruby
# Get all the review queue for all the teams
client.moderation.query_review_queue(GetStream::Generated::Models::QueryReviewQueueRequest.new(
  limit: 2,
))

# Get all the review queue for the team `team-a`
client.moderation.query_review_queue(GetStream::Generated::Models::QueryReviewQueueRequest.new(
  filter: { "teams" => "team-a" },
  sort: [GetStream::Generated::Models::SortParam.new(field: "created_at", direction: -1)],
  limit: 2,
))

# Get all the review queue for the team `team-a` and `team-b`
client.moderation.query_review_queue(GetStream::Generated::Models::QueryReviewQueueRequest.new(
  filter: { "teams" => { "$in" => ["team-a", "team-b"] } },
  sort: [GetStream::Generated::Models::SortParam.new(field: "created_at", direction: -1)],
  limit: 2,
))
```

</codetabs-item>

<codetabs-item value="dotnet" label=".NET">

```csharp
// Get all the review queue for all the teams
await client.Moderation.QueryReviewQueueAsync(new QueryReviewQueueRequest
{
    Limit = 2,
});

// Get all the review queue for the team `team-a`
await client.Moderation.QueryReviewQueueAsync(new QueryReviewQueueRequest
{
    Filter = new Dictionary<string, object> { { "teams", "team-a" } },
    Sort = new List<SortParam>
    {
        new SortParam { Field = "created_at", Direction = -1 },
    },
    Limit = 2,
});

// Get all the review queue for the team `team-a` and `team-b`
await client.Moderation.QueryReviewQueueAsync(new QueryReviewQueueRequest
{
    Filter = new Dictionary<string, object>
    {
        { "teams", new Dictionary<string, object> { { "$in", new[] { "team-a", "team-b" } } } },
    },
    Sort = new List<SortParam>
    {
        new SortParam { Field = "created_at", Direction = -1 },
    },
    Limit = 2,
});
```

</codetabs-item>

</codetabs>

### Client Side Integration

The client-side review queue access enables building custom moderation dashboards:

<codetabs>

<codetabs-item value="js" label="Node">

```js
// Get all the review queue for all the teams that the user belongs to.
const { reviews, next } = await client.moderation.queryReviewQueue({
  limit: 2,
});

// Get all the review queue for the team `team-a`
// This will return all the review queue for the team `team-a`, only if the user belongs to `team-a`
const { reviews, next } = await client.moderation.queryReviewQueue({
  filter: { team: "team-a" },
  sort: [{ field: "created_at", direction: -1 }],
  limit: 2,
});
```

</codetabs-item>

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

```py
# Get all the review queue for all the teams that the user belongs to.
client.moderation().query_review_queue(limit=2)

# Get all the review queue for the team `team-a`
client.moderation().query_review_queue(
    filter={"team": "team-a"},
    sort=[{"field": "created_at", "direction": -1}],
    limit=2,
)
```

</codetabs-item>

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

```go
// Get all the review queue for all the teams that the user belongs to.
client.Moderation().QueryReviewQueue(ctx, &getstream.QueryReviewQueueRequest{
    Limit: getstream.PtrTo(2),
})

// Get all the review queue for the team `team-a`
client.Moderation().QueryReviewQueue(ctx, &getstream.QueryReviewQueueRequest{
    Filter: map[string]interface{}{"team": "team-a"},
    Sort:   []getstream.SortParam{{Field: "created_at", Direction: getstream.PtrTo(-1)}},
    Limit:  getstream.PtrTo(2),
})
```

</codetabs-item>

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

```java
// Get all the review queue for all the teams that the user belongs to.
client.moderation().queryReviewQueue(QueryReviewQueueRequest.builder()
    .limit(2)
    .build())
    .execute();

// Get all the review queue for the team `team-a`
client.moderation().queryReviewQueue(QueryReviewQueueRequest.builder()
    .filter(Map.of("team", "team-a"))
    .sort(List.of(SortParam.builder().field("created_at").direction(-1).build()))
    .limit(2)
    .build())
    .execute();
```

</codetabs-item>

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

```php
// Get all the review queue for all the teams that the user belongs to.
$client->moderation()->queryReviewQueue(new QueryReviewQueueRequest(
    limit: 2,
));

// Get all the review queue for the team `team-a`
$client->moderation()->queryReviewQueue(new QueryReviewQueueRequest(
    filter: ['team' => 'team-a'],
    sort: [new SortParam(field: 'created_at', direction: -1)],
    limit: 2,
));
```

</codetabs-item>

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

```ruby
# Get all the review queue for all the teams that the user belongs to.
client.moderation.query_review_queue(GetStream::Generated::Models::QueryReviewQueueRequest.new(
  limit: 2,
))

# Get all the review queue for the team `team-a`
client.moderation.query_review_queue(GetStream::Generated::Models::QueryReviewQueueRequest.new(
  filter: { "team" => "team-a" },
  sort: [GetStream::Generated::Models::SortParam.new(field: "created_at", direction: -1)],
  limit: 2,
))
```

</codetabs-item>

<codetabs-item value="dotnet" label=".NET">

```csharp
// Get all the review queue for all the teams that the user belongs to.
await client.Moderation.QueryReviewQueueAsync(new QueryReviewQueueRequest
{
    Limit = 2,
});

// Get all the review queue for the team `team-a`
await client.Moderation.QueryReviewQueueAsync(new QueryReviewQueueRequest
{
    Filter = new Dictionary<string, object> { { "team", "team-a" } },
    Sort = new List<SortParam>
    {
        new SortParam { Field = "created_at", Direction = -1 },
    },
    Limit = 2,
});
```

</codetabs-item>

</codetabs>

<admonition type="tip">
Consider implementing pagination when displaying review queue items to improve performance and user experience. The `next` parameter can be used to fetch subsequent pages of results.
</admonition>

## Team level blocklists

<admonition type="note">
Team level blocklists are behind a feature flag and are not enabled by default. Please reach out to support if you are interested in enabling this feature.
</admonition>

Team level blocklists gives your customers the ability to manage blocklists for their team. These blocklists can be used in moderation policies to flag or remove messages that contain specific words or phrases.

CRUD APIs to maintain team level blocklists are accessible on client side integration. This means that you can build a custom moderation dashboard for your teams to maintain the blocklists. Its important to note that team level blocklists can only be created/updated/deleted by team admins and moderators who have the following permissions:

- `CreateBlockList`
- `UpdateBlockList`
- `ReadBlockLists`
- `DeleteBlockList`

### Limitations

- Maximum 4 blocklists per team
- Maximum 20,000 blocklists per app (including global blocklists)
- Maximum 200,000 words in total across all blocklists of an app

<admonition type="tip">
Since global (non-team) blocklists can be used in team-level moderation configs, it's recommended to maintain global blocklists for common scenarios shared across teams.
</admonition>

### Create a Team Level Blocklist

You can create a blocklist specific to a team using the following code:

###

<codetabs>

<codetabs-item value="js" label="Node">

```js
await client.createBlockList({
  name: "myblocklist",
  team: "team-a",
  words: ["bad-word-1", "bad-word-2"],
  type: "word",
});
```

</codetabs-item>

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

```py
client.create_block_list(
    name="myblocklist",
    team="team-a",
    words=["bad-word-1", "bad-word-2"],
    type="word",
)
```

</codetabs-item>

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

```go
client.CreateBlockList(ctx, &getstream.CreateBlockListRequest{
    Name:  "myblocklist",
    Team:  getstream.PtrTo("team-a"),
    Words: []string{"bad-word-1", "bad-word-2"},
    Type:  "word",
})
```

</codetabs-item>

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

```java
client.createBlockList(CreateBlockListRequest.builder()
    .name("myblocklist")
    .team("team-a")
    .words(List.of("bad-word-1", "bad-word-2"))
    .type("word")
    .build())
    .execute();
```

</codetabs-item>

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

```php
$client->createBlockList(new CreateBlockListRequest(
    name: 'myblocklist',
    team: 'team-a',
    words: ['bad-word-1', 'bad-word-2'],
    type: 'word',
));
```

</codetabs-item>

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

```ruby
client.create_block_list(GetStream::Generated::Models::CreateBlockListRequest.new(
  name: "myblocklist",
  team: "team-a",
  words: ["bad-word-1", "bad-word-2"],
  type: "word",
))
```

</codetabs-item>

<codetabs-item value="dotnet" label=".NET">

```csharp
await client.CreateBlockListAsync(new CreateBlockListRequest
{
    Name = "myblocklist",
    Team = "team-a",
    Words = new List<string> { "bad-word-1", "bad-word-2" },
    Type = "word",
});
```

</codetabs-item>

</codetabs>

### Update a Team Level Blocklist

To update an existing team blocklist:

<codetabs>

<codetabs-item value="js" label="Node">

```js
await client.updateBlockList("myblocklist", {
  words: ["test3", "test4"],
  team: "team-a",
});
```

</codetabs-item>

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

```py
client.update_block_list("myblocklist",
    words=["test3", "test4"],
    team="team-a",
)
```

</codetabs-item>

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

```go
client.UpdateBlockList(ctx, "myblocklist", &getstream.UpdateBlockListRequest{
    Words: []string{"test3", "test4"},
    Team:  getstream.PtrTo("team-a"),
})
```

</codetabs-item>

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

```java
client.updateBlockList("myblocklist", UpdateBlockListRequest.builder()
    .words(List.of("test3", "test4"))
    .team("team-a")
    .build())
    .execute();
```

</codetabs-item>

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

```php
$client->updateBlockList('myblocklist', new UpdateBlockListRequest(
    words: ['test3', 'test4'],
    team: 'team-a',
));
```

</codetabs-item>

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

```ruby
client.update_block_list("myblocklist", GetStream::Generated::Models::UpdateBlockListRequest.new(
  words: ["test3", "test4"],
  team: "team-a",
))
```

</codetabs-item>

<codetabs-item value="dotnet" label=".NET">

```csharp
await client.UpdateBlockListAsync("myblocklist", new UpdateBlockListRequest
{
    Words = new List<string> { "test3", "test4" },
    Team = "team-a",
});
```

</codetabs-item>

</codetabs>

### Delete a Team Level Blocklist

To delete a team blocklist:

<codetabs>

<codetabs-item value="js" label="Node">

```js
await client.deleteBlockList("myblocklist", {
  team: "team-a",
});
```

</codetabs-item>

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

```py
client.delete_block_list("myblocklist", team="team-a")
```

</codetabs-item>

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

```go
client.DeleteBlockList(ctx, "myblocklist", &getstream.DeleteBlockListRequest{
    Team: getstream.PtrTo("team-a"),
})
```

</codetabs-item>

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

```java
client.deleteBlockList("myblocklist", DeleteBlockListRequest.builder()
    .team("team-a")
    .build())
    .execute();
```

</codetabs-item>

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

```php
$client->deleteBlockList('myblocklist', new DeleteBlockListRequest(
    team: 'team-a',
));
```

</codetabs-item>

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

```ruby
client.delete_block_list("myblocklist", GetStream::Generated::Models::DeleteBlockListRequest.new(
  team: "team-a",
))
```

</codetabs-item>

<codetabs-item value="dotnet" label=".NET">

```csharp
await client.DeleteBlockListAsync("myblocklist", new DeleteBlockListRequest
{
    Team = "team-a",
});
```

</codetabs-item>

</codetabs>

### List Blocklists

You can list both global and team-specific blocklists:

<codetabs>

<codetabs-item value="js" label="Node">

```js
// List global blocklists
const globalBlocklists = await client.listBlocklists();

// List team specific blocklists
const teamBlocklists = await client.listBlocklists({ team: "team-a" });
```

</codetabs-item>

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

```py
# List global blocklists
client.list_blocklists()

# List team specific blocklists
client.list_blocklists(team="team-a")
```

</codetabs-item>

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

```go
// List global blocklists
client.ListBlocklists(ctx, &getstream.ListBlocklistsRequest{})

// List team specific blocklists
client.ListBlocklists(ctx, &getstream.ListBlocklistsRequest{
    Team: getstream.PtrTo("team-a"),
})
```

</codetabs-item>

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

```java
// List global blocklists
client.listBlocklists().execute();

// List team specific blocklists
client.listBlocklists(ListBlocklistsRequest.builder()
    .team("team-a")
    .build())
    .execute();
```

</codetabs-item>

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

```php
// List global blocklists
$client->listBlocklists();

// List team specific blocklists
$client->listBlocklists(new ListBlocklistsRequest(team: 'team-a'));
```

</codetabs-item>

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

```ruby
# List global blocklists
client.list_blocklists

# List team specific blocklists
client.list_blocklists(GetStream::Generated::Models::ListBlocklistsRequest.new(team: "team-a"))
```

</codetabs-item>

<codetabs-item value="dotnet" label=".NET">

```csharp
// List global blocklists
await client.ListBlocklistsAsync();

// List team specific blocklists
await client.ListBlocklistsAsync(new ListBlocklistsRequest
{
    Team = "team-a",
});
```

</codetabs-item>

</codetabs>

### Using Blocklists in Team Level Moderation Configuration

You can use both global and team level blocklists in your team's moderation configuration:

<codetabs>

<codetabs-item value="js" label="Node">

```js
await client.moderation.upsertConfig({
  key: "chat:messaging",
  team: "team-a",
  blocklist_config: {
    rules: [
      // team level blocklist
      {
        name: "my_team_blocklist",
        team: "team-a",
        action: "remove",
      },
      // global blocklist
      {
        name: "my_global_blocklist",
        action: "flag",
      },
    ],
  },
});
```

</codetabs-item>

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

```py
client.moderation().upsert_config(
    key="chat:messaging",
    team="team-a",
    blocklist_config={
        "rules": [
            # team level blocklist
            {"name": "my_team_blocklist", "team": "team-a", "action": "remove"},
            # global blocklist
            {"name": "my_global_blocklist", "action": "flag"},
        ],
    },
)
```

</codetabs-item>

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

```go
client.Moderation().UpsertConfig(ctx, &getstream.UpsertConfigRequest{
    Key:  "chat:messaging",
    Team: getstream.PtrTo("team-a"),
    BlocklistConfig: &getstream.BlocklistConfig{
        Rules: []getstream.BlocklistRule{
            // team level blocklist
            {Name: "my_team_blocklist", Team: getstream.PtrTo("team-a"), Action: "remove"},
            // global blocklist
            {Name: "my_global_blocklist", Action: "flag"},
        },
    },
})
```

</codetabs-item>

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

```java
client.moderation().upsertConfig(UpsertConfigRequest.builder()
    .key("chat:messaging")
    .team("team-a")
    .blocklistConfig(BlocklistConfig.builder()
        .rules(List.of(
            // team level blocklist
            BlocklistRule.builder().name("my_team_blocklist").team("team-a").action("remove").build(),
            // global blocklist
            BlocklistRule.builder().name("my_global_blocklist").action("flag").build()))
        .build())
    .build())
    .execute();
```

</codetabs-item>

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

```php
$client->moderation()->upsertConfig(new UpsertConfigRequest(
    key: 'chat:messaging',
    team: 'team-a',
    blocklistConfig: new BlocklistConfig(
        rules: [
            // team level blocklist
            new BlocklistRule(name: 'my_team_blocklist', team: 'team-a', action: 'remove'),
            // global blocklist
            new BlocklistRule(name: 'my_global_blocklist', action: 'flag'),
        ],
    ),
));
```

</codetabs-item>

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

```ruby
client.moderation.upsert_config(GetStream::Generated::Models::UpsertConfigRequest.new(
  key: "chat:messaging",
  team: "team-a",
  blocklist_config: GetStream::Generated::Models::BlocklistConfig.new(
    rules: [
      # team level blocklist
      GetStream::Generated::Models::BlocklistRule.new(name: "my_team_blocklist", team: "team-a", action: "remove"),
      # global blocklist
      GetStream::Generated::Models::BlocklistRule.new(name: "my_global_blocklist", action: "flag"),
    ],
  ),
))
```

</codetabs-item>

<codetabs-item value="dotnet" label=".NET">

```csharp
await client.Moderation.UpsertConfigAsync(new UpsertConfigRequest
{
    Key = "chat:messaging",
    Team = "team-a",
    BlocklistConfig = new BlocklistConfig
    {
        Rules = new List<BlocklistRule>
        {
            // team level blocklist
            new BlocklistRule { Name = "my_team_blocklist", Team = "team-a", Action = "remove" },
            // global blocklist
            new BlocklistRule { Name = "my_global_blocklist", Action = "flag" },
        },
    },
});
```

</codetabs-item>

</codetabs>

## Dashboard Support for Multitenancy Moderation

You can manage team level moderation policies and blocklists from the dashboard as following.

### Create Team Level Moderation Policy

You can create a team level moderation policy from the dashboard as following:

- Navigate to "Policies" page in left sidebar
- Click on "+ Add New" button, which will open a modal to create a new moderation policy
- Select "Chat" as product
- On this modal, you will see an input field to assign a team to the policy

Once you press "Save" button, the policy will be created for the team you selected.

In the policies list, you will see a team column which will show the team to which the policy belongs.

### Team Level Blocklists

You can create a team blocklist from the dashboard as following:

- Navigate to "Policies" page in left sidebar
- Select a team policy (or team level moderation configuration) where you want to add the blocklist
- Scroll down to "Blocklists & Regex Filters" section
- Click on "+ Add New" button, which will open a modal to create a new blocklist
- On this modal, you will see a checkbox to mark this blocklist as team level blocklist

The team blocklists will be shown in the moderation policy as following:


---

This page was last updated at 2026-04-16T18:29:40.193Z.

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