Flag, Mute & Ban

Overview

Stream provides moderation APIs for flagging content, muting users, banning users, and blocking users. These actions integrate with the moderation review queue and dashboard. All endpoints use the v2 moderation API and are available through Stream's server-side SDKs.

Not every action works outside Stream's products. Flag, ban, and bypass also work for your own (standalone) content and users. Mute, block, and channel bans only take effect inside Stream Chat, Feeds, and Video.

Action Standalone content and users Stream Chat, Feeds, Video
Flag Yes. Any entity_type; custom types need server-side auth Yes
Ban Yes. Stream records the ban; your app enforces it Yes. Stream enforces it
Channel ban No Stream Chat only
Mute No Yes
Block No Yes
Bypass Yes. Skips Check for the user's content Yes

Flag

Flagging allows users or moderators to report content for review. Flagged content is automatically added to the moderation review queue. You can flag messages, users, activities, reactions, or any custom entity type.

Flag Content

Use the flag method to report an entity for moderation review. You can attach a reason, custom metadata, and a moderation payload containing the content to be reviewed.

client.moderation.flag(GetStream::Generated::Models::FlagRequest.new(
  entity_type: "stream:chat:v1:message",
  entity_id: message_id,
  entity_creator_id: creator_id,
  reason: "spam",
  custom: { "user_comment" => "This user is spamming the channel" },
  user_id: moderator_id
))

Request parameters

key required type description
entity_type true string Type of entity being flagged. Examples: stream:chat:v1:message, stream:user, stream:feeds:v2:activity, stream:feeds:v2:reaction, or any custom type.
entity_id true string Unique identifier of the entity being flagged.
entity_creator_id false string User ID of the entity creator.
reason false string Reason for flagging. Use a slug or keyword for easy filtering.
custom false object Custom metadata to attach to the flag.
moderation_payload false object Content to be reviewed (texts, images, videos). Displayed on the dashboard.
user_id false string User ID of the person flagging (required for server-side usage).

Response

key type description
item_id string ID of the review queue item created by the flag.

Flag a User

To flag a user rather than a message, set entity_type to stream:user and provide the target user's ID as entity_id.

client.moderation.flag(GetStream::Generated::Models::FlagRequest.new(
  entity_type: "stream:user",
  entity_id: target_user_id,
  reason: "harassment",
  user_id: moderator_id
))

Query Moderation Flags

Query flags with filtering, sorting, and pagination. This allows you to retrieve flags matching specific criteria.

response = client.moderation.query_moderation_flags(GetStream::Generated::Models::QueryModerationFlagsRequest.new(
  filter: { "entity_type" => "stream:chat:v1:message" },
  sort: [GetStream::Generated::Models::SortParam.new(field: "created_at", direction: -1)],
  limit: 20
))

Request parameters

key required type description
filter false object Filter conditions.
sort false array Sort parameters.
limit false number Maximum flags to return.
next false string Pagination cursor.

Mute

Info:

Mute only takes effect inside Stream Chat, Feeds, and Video. For your own app, store mutes in your own system.

Muting allows users to silence other users. Muted users' messages are still delivered via WebSocket but not via push notifications (APN/Firebase). Implementing UI logic for muted users (hiding or displaying differently) is left to the developer.

Mute User

Mute one or more users. You can optionally set a timeout after which the mute automatically expires.

client.moderation.mute(GetStream::Generated::Models::MuteRequest.new(
  target_ids: ["user_to_mute"],
  user_id: muting_user_id,
  timeout: 60
))

Request parameters

key required type description
target_ids true array List of user IDs to mute.
user_id false string User ID of the person performing the mute.
timeout false number Mute duration in minutes. If not set, the mute has no expiration.

Unmute User

Remove a mute from one or more users.

client.moderation.unmute(GetStream::Generated::Models::UnmuteRequest.new(
  target_ids: ["user_to_unmute"],
  user_id: unmuting_user_id
))

Ban

Banning stops a user from posting. An app-wide ban works for any user: Stream records it on the user (banned, ban_expires) and sends a user.banned webhook. Stream Chat and Feeds enforce it for you; in your own app, read the ban state or handle the webhook and block the user yourself. Stream Chat users can also be banned from a single channel. Bans support timeouts (temporary bans), shadow banning, IP banning, and optional message deletion.

Ban User

Ban a user from the entire app. You can optionally specify a timeout for temporary bans, a reason, and whether to delete the user's messages.

client.moderation.ban(GetStream::Generated::Models::BanRequest.new(
  target_user_id: "user_to_ban",
  banned_by_id: moderator_id,
  reason: "Repeated spam",
  timeout: 1440
))

Request parameters

key required type description
target_user_id true string User ID to ban.
banned_by_id false string User ID of the moderator performing the ban.
channel_cid false string Channel CID for channel-scoped bans (e.g., messaging:general). Omit for app-wide bans.
reason false string Reason for the ban.
timeout false number Ban duration in minutes. If not set, the ban is permanent.
shadow false boolean When true, the user is shadow banned. Their messages are only visible to themselves.
ip_ban false boolean When true, also bans the user's last known IP address. Automatically applies a 30-day timeout unless overridden.
delete_messages false string Delete the user's messages. Values: soft (removes from client, retained on server) or hard (permanently deleted).

Channel-Scoped Ban

Stream Chat only. Ban a user from a specific channel by providing the channel_cid parameter. The user will still be able to use the rest of the app.

client.moderation.ban(GetStream::Generated::Models::BanRequest.new(
  target_user_id: "user_to_ban",
  banned_by_id: moderator_id,
  channel_cid: "messaging:general",
  reason: "Trolling in this channel",
  timeout: 60
))

Shadow Ban

Shadow banning allows a user to continue posting, but their messages are only visible to themselves. Other users will not see the shadow-banned user's messages. This is useful for handling disruptive users without alerting them.

client.moderation.ban(GetStream::Generated::Models::BanRequest.new(
  target_user_id: "user_to_shadow_ban",
  banned_by_id: moderator_id,
  reason: "Disruptive behavior",
  shadow: true
))

Unban User

Remove a ban from a user. For channel-scoped bans, provide the channel_cid parameter.

client.moderation.unban(GetStream::Generated::Models::UnbanRequest.new(
  target_user_id: "user_to_unban",
  created_by: moderator_id
))

Query Banned Users

List bans with the metadata that is intentionally not returned by queryUsers — the moderator who issued the ban (banned_by), the original reason, when it expires, and the channel CID for channel-scoped bans. Use this when you need to render a "who banned this user and why" view, build a moderation dashboard, or audit ban history.

Note:

queryUsers returns only the ban state (banned, ban_expires) on the user object — banned_by and reason are not part of the user payload by design. Use queryBannedUsers to retrieve them. This endpoint is part of the Chat product API; bans are app-wide, so it works for any app that has Chat enabled regardless of which product applied the ban.

Client-side the endpoint is restricted to a single channel: a request without a channel_cid filter is rejected, so app-wide bans can only be listed server-side. Both regular and shadow bans are returned, and so are bans that have already expired unless exclude_expired_bans is set. To list everyone banned from one channel, including future channel bans, use Query Channel Bans.

response = client.chat.query_banned_users(
  GetStream::Generated::Models::QueryBannedUsersPayload.new(
    filter_conditions: { 'user_id' => { '$eq' => 'user_to_ban' } },
    sort: [{ 'field' => 'created_at', 'direction' => -1 }],
    limit: 20,
    exclude_expired_bans: true,
  )
)

Request parameters

key required type description
filter_conditions true object Filter conditions. Supported keys include user_id, banned_by_id, channel_cid, reason, created_at, expires. Operators: $eq, $in, $gt, $gte, $lt, $lte.
sort false array Sort parameters (max 1). Typically sort by created_at.
limit false number Number of records to return (max 300).
offset false number Number of records to skip (max 10000).
exclude_expired_bans false boolean When true, omits bans whose expires timestamp is in the past.
user_id false string Server-side actor making the request.

Response

key type description
bans array List of bans. Each entry contains the fields described below.

Each entry in bans:

key type description
user object The banned user.
banned_by object The moderator who applied the ban (the answer to "who banned this user?").
reason string Reason supplied when the ban was created.
expires string Expiration timestamp for temporary bans. Absent for permanent bans.
shadow bool true for shadow bans.
channel object Present for channel-scoped bans; contains the channel CID and metadata.
created_at string When the ban was applied.

Query Channel Bans

List every user banned from one channel in a single cursor-paginated call, with the scope of each ban. An entry's scopes is ["channel"] for a ban on the channel itself, ["future_channel"] for a future channel ban issued by the channel's creator, or both. Each user appears once, so you do not need to page queryBannedUsers and queryFutureChannelBans separately and merge the lists yourself. Use it to render a channel's ban list, search it by user ID, and show per-scope counts.

response = client.chat.query_channel_bans(
  'messaging',
  'general',
  GetStream::Generated::Models::QueryChannelBansRequest.new(
    filter_conditions: { 'user_id' => { '$autocomplete' => 'jo' } },
    exclude_expired_bans: true,
    include_total: true,
    limit: 25
  )
)

The Python SDK does not expose this endpoint yet; its method arrives with the next stream-py release.

Request parameters

key required type description
filter_conditions false object Only user_id is supported, with $eq, $in (up to 100 IDs) and $autocomplete (case-insensitive prefix of up to 80 characters). Omit it, or pass {}, to list every banned user.
exclude_expired_bans false boolean When true, omits bans whose expires timestamp is in the past.
include_total false boolean When true, the response includes total and total_by_scope, counted over the whole list regardless of paging.
limit false number Entries per page, 1 to 100. Defaults to 25.
next false string Cursor from a previous response, to fetch the page after it.
prev false string Cursor from a previous response, to fetch the page before it. Cannot be combined with next.
user_id false string Server-side actor making the request.

Entries are ordered newest ban first: by created_at descending, then by user_id descending. Every entry is a distinct user, so the next and prev cursors never skip or repeat an entry, even when bans are added or removed between pages. A user banned by both scopes is placed by the channel ban's created_at.

Response

key type description
bans array One entry per banned user, described below.
next string Cursor for the next page. Absent on the last page.
prev string Cursor for the previous page. Absent on the first page.
total number Number of distinct banned users. Only present when include_total is true.
total_by_scope object channel and future_channel counts. A user with both scopes is counted in each. Only present when include_total is true.

Each entry in bans:

key type description
user object The banned user. Absent when the user has since been hard-deleted.
scopes array channel, future_channel, or both.
created_at string When the ban was created. The channel ban's timestamp when the user has one, otherwise the future channel ban's.
channel_ban object Details of the ban on this channel. Present when scopes contains channel.
future_channel_ban object Details of the channel creator's future channel ban against this user. Present when scopes contains future_channel.

channel_ban and future_channel_ban share one shape:

key type description
banned_by object The user who issued the ban.
reason string Reason supplied when the ban was created.
shadow boolean true for shadow bans.
expires string Expiration timestamp for temporary bans. Absent for permanent bans.
created_at string When this ban was created.
Note:

App-wide bans are not part of this list; use Query Banned Users for those. Totals are counted per scope with a cap of 100,000 each, and both total fields are omitted when counting times out, while the page itself is still returned. Client-side requests need the BanChannelMember permission on the channel, which channel moderators have by default.


Future Channel Bans

A future channel ban keeps a user out of channels that do not exist yet. It is scoped to a channel creator: whenever that creator adds the banned user to a channel they own, or creates a channel with the user as a member, the user is banned in that channel automatically. Existing memberships are left alone.

Typical uses:

  • Marketplace sellers keeping a problematic buyer out of every product channel they open later.
  • Community owners who keep creating new channels and want a known offender excluded from all of them.
  • Creators enforcing one moderation decision across every channel they make.

Create a Future Channel Ban

Pass ban_from_future_channels: true to a channel-scoped ban. This bans the user from the channel right away and records a future channel ban owned by the channel's creator. reason, shadow and timeout apply to both.

client.moderation.ban(GetStream::Generated::Models::BanRequest.new(
  target_user_id: "user_to_ban",
  banned_by_id: moderator_id,
  channel_cid: "messaging:general",
  ban_from_future_channels: true,
  reason: "Repeated harassment"
))

Request parameters

key required type description
ban_from_future_channels false boolean Also ban the user from channels the channel's creator makes in the future.
channel_cid false string The channel to ban from. The future channel ban is scoped to this channel's creator. Without it, the user is banned app-wide and the future channel ban is scoped to the moderator in banned_by_id.
timeout false number Minutes until the channel ban and the future channel ban expire. Omit for a permanent ban.
shadow false boolean Make the channel ban, and every ban later derived from the future channel ban, a shadow ban.
reason false string Reason, copied to every ban derived from the future channel ban.
Note:

ban_from_future_channels without channel_cid cannot be combined with ip_ban. From the review queue, set the same flag on a ban action; see Ban from Future Channels.

How Future Channel Bans Are Applied

When the creator adds members to a channel they created, or creates a channel with members, each new member is checked against the creator's active future channel bans. A match marks the member as banned in that channel and creates a channel ban that inherits reason and shadow. The derived channel ban has no expiration of its own, so it stays until the user is unbanned from that channel.

  • Only the channel creator's future channel bans apply. If another user creates a channel and adds the target, nothing happens.
  • Members already in a channel are not affected. A future channel ban is not retroactive.
  • A ban on the channel itself takes precedence when both exist.

Channel member objects show the link. ban_from_future_channels is true on a banned member who is also covered by an active future channel ban from the channel's creator, and future_channel_ban_expires carries that ban's expiration when it has one. Both fields appear on channel state and query members responses, not on events or webhooks.

Future Channel Ban Expiration

timeout applies to the future channel ban itself, and to the channel ban created with it. Once it expires, no new channel picks up the ban, but channel bans already derived from it stay in place until removed.

Remove a Future Channel Ban

Submit an unban action with remove_future_channels_ban: true. The action removes the future channel ban owned by the acting user, so user_id must be the channel creator the ban is scoped to. Pass channel_cid to also lift the ban on that channel. Channel bans derived from the future channel ban in other channels are left in place and need their own unban.

client.moderation.submit_action(GetStream::Generated::Models::SubmitActionRequest.new(
  action_type: "unban",
  unban: GetStream::Generated::Models::UnbanActionRequest.new(
    target_user_id: "user_to_unban",
    channel_cid: "messaging:general",
    remove_future_channels_ban: true
  ),
  user_id: channel_creator_id
))
Note:

The Unban User endpoint only lifts the channel or app-wide ban. It does not remove future channel bans.

Query Future Channel Bans

List the future channel bans a creator has issued, or every future channel ban against one user. To see a channel's bans across both scopes, use Query Channel Bans instead.

response = client.chat.query_future_channel_bans(
  GetStream::Generated::Models::QueryFutureChannelBansPayload.new(
    user_id: creator_id,
    exclude_expired_bans: true,
    include_total: true,
    limit: 50
  )
)

Request parameters

key required type description
user_id false string The creator whose future channel bans to list. Server-side, one of user_id or target_user_id is required. Client-side requests are always scoped to the authenticated user.
target_user_id false string The banned user. With user_id, narrows that creator's bans to this user. On its own, lists every creator's bans against this user.
exclude_expired_bans false boolean When true, omits future channel bans whose expires timestamp is in the past.
include_total false boolean When true, the response includes total: the number of bans matching the filter regardless of paging, capped at 100,000.
limit false number Number of records to return (max 300).
offset false number Number of records to skip (max 10000).

Response

key type description
bans array List of future channel bans. Each entry contains the fields below.
total number Matching bans, ignoring paging. Only present when include_total is true.

Each entry in bans:

key type description
user object The banned user.
banned_by object The creator who owns the future channel ban.
reason string Reason supplied when the ban was created.
shadow boolean true for shadow bans.
expires string Expiration timestamp for temporary bans. Absent for permanent bans.
created_at string When the future channel ban was created.

Events

Creating a future channel ban sends the usual user.banned event for the channel ban. The future channel ban itself has no event of its own.

When a future channel ban is applied to a new channel, the membership event (member.added, or channel.created for a new channel) is followed by a user.banned event for that channel. Its created_by is the channel creator, reason and shadow come from the future channel ban, and it carries no expiration.

Removing a future channel ban sends no event. Channel bans derived from it that you remove later send their own user.unbanned events.


Block

Blocking only takes effect inside Stream Chat, Feeds, and Video, and it is not a moderation method. Call blockUsers on the common client (client.blockUsers in Node, client.block_users in Python, client.common.block_users in Ruby, client.BlockUsers in Go, client.blockUsers in Java and PHP). The body is blocked_user_id and user_id.

client.common.block_users(GetStream::Generated::Models::BlockUsersRequest.new(
  blocked_user_id: "user_to_block",
  user_id: blocking_user_id
))

Bypass Moderation

Enable or disable moderation bypass for a specific user, allowing them to skip content moderation checks. This is a server-side only operation intended for trusted users whose content should not be subject to automated moderation. Bypass applies to the user's Stream Chat and Feeds content and to Check calls for any entity type, where entity_creator_id is the user. See Bypass Moderation for permission-based bypass in Stream Chat.

client.moderation.bypass(GetStream::Generated::Models::BypassRequest.new(
  target_user_id: "trusted_user_id",
  enabled: true
))

Request parameters

key required type description
target_user_id true string User ID to enable or disable bypass for.
enabled true boolean true to enable bypass, false to disable.