Moderation is how trusted users keep a conversation clean: a support lead removing an abusive message, or a community moderator deleting spam.
Where this fits: your backend decides who is a moderator by giving them
the MODERATOR or ADMIN role when it adds them as a
member. Their app then deletes messages with the normal
delete call. Bans, mutes, blocks and reports use chat.moderation,
described below.
How it works
A moderator deletes a message with the same call anyone uses for their own messages. The server looks at the user's role and decides if it's allowed. So your app doesn't need a special "moderator mode"; just show the delete button to moderators too.
In your app:
await chat.messages.delete('msg_3xR…');await livqeno.chat!.messages.delete('msg_3xR…');await chat.delete(messageId);Everyone in the conversation gets a messageDeleted event, so the message
disappears from every screen.
Who can delete what
| Role | Can delete |
|---|---|
MEMBER | Only their own messages |
MODERATOR or ADMIN | Anyone's message in that conversation (the chat:moderate scope; see Authentication) |
- Deleting is soft. The message stays with
deletedAtset, so you can review it later. Your backend can load deleted messages withincludeDeletedin Message history. - Moderators can't edit other people's messages. Only the author can.
- To stop someone sending without removing them, a moderator can mute or ban them. To take them out entirely, remove them.
Make someone a moderator
On your backend, set the role when you add the member. To change it, add them again with the new role:
await livqeno.chat.addMember('support-room-42', { userId: 'carol', role: 'MODERATOR' });This is a backend-only call. No app can give itself or anyone a role. See Members.
Example: in a support app, make every agent a MODERATOR of the tickets
they handle. Customers stay MEMBERs, so they can only delete their own
messages.
Live Streaming
A live stream's host and co-hosts automatically get ADMIN / MODERATOR
rights in the stream's chat. See
Live Streaming → Moderation.
Mute and ban from the app
A MODERATOR or ADMIN of a conversation can mute or ban people in that
conversation straight from your app. The server checks their role on every
call.
- Mute: the user can still read, but can't send, react or edit.
- Ban: the user is disconnected from the conversation at once and can't rejoin, read history or send until the ban ends.
Leave out durationSeconds for a permanent action.
await chat.moderation.mute('bob', { durationSeconds: 600, reason: 'spam' });
await chat.moderation.unmute('bob');
await chat.moderation.ban('bob', { room: 'conv_…', durationSeconds: 86_400 });
await chat.moderation.unban('bob', { room: 'conv_…' });await livqeno.chat!.moderation.mute('bob', { durationSeconds: 600 });await chat.mute('bob', duration: const Duration(minutes: 10));
await chat.ban('bob', room: conversationId, reason: 'spam');
await chat.unban('bob', room: conversationId);room defaults to the conversation you connected to. App-wide bans, and
the slow-mode setting, are backend calls made with your API key; see the
Chat API.
Moderation events
Everyone in the conversation is told:
| Event | Payload |
|---|---|
memberBanned / memberUnbanned | { roomId, userId, by, expiresAt } |
memberMuted / memberUnmuted | { roomId, userId, by, expiresAt } |
expiresAt is null for a permanent action. The banned user also gets
removedFromRoom with reason: 'BANNED', and the SDK stops rejoining that
conversation on reconnect:
chat.on('removedFromRoom', ({ roomId, reason }) => {
if (reason === 'BANNED') showBannedNotice(roomId);
});
// This user's own state, from the events (and refused sends) seen so far:
const { muted, mutedUntil, banned } = chat.moderation.getMyState('conv_…');There's no "am I muted?" query, so a user who connects while already muted
finds out on their first refused send. getMyState() updates from that
too. In React, useModeration(room) gives you the same state.
Block another user
Any user can block another. The blocker stops receiving the blocked user's messages, reactions and typing, and stops seeing their presence. In a DIRECT conversation, neither of them can send while the block is in place. The blocked user isn't told.
await chat.moderation.block('bob');
await chat.moderation.unblock('bob');
const blocks = await chat.moderation.listBlocks(); // [{ userId, createdAt }]await livqeno.chat!.moderation.block('bob');await chat.block('bob');
await chat.unblock('bob');
final blocks = await chat.listBlocks();Report a message
Any member can flag a message for review. The message stays visible; your
team reviews the queue in the dashboard or through the API, and your
backend receives a message.flagged webhook.
await chat.moderation.flagMessage('msg_3xR…', 'spam', 'Posted the same link 20 times');reason is one of spam, harassment, hate, sexual, violence,
other. On Flutter: await chat.flagMessage(messageId, LivqenoFlagReason.spam).
Errors when sending
A send the moderation rules refuse rejects with a typed error, and the
message is left in the outbox as failed:
| Code | Error class | Meaning |
|---|---|---|
MUTED | LivqenoModerationError | A moderator muted this user here. |
BANNED | LivqenoModerationError | Banned from this conversation, or app-wide. |
BLOCKED | LivqenoModerationError | A DIRECT conversation where one side blocked the other. |
SLOW_MODE | LivqenoSlowModeError | Slow mode is on. Wait error.retryAfterSeconds. Moderators and admins are exempt. |
MESSAGE_BLOCKED | LivqenoMessageBlockedError | Your project's moderation hook or profanity filter refused it. error.reason says why (moderation_unavailable if the hook couldn't be reached and it fails closed). |
RATE_LIMITED | LivqenoRateLimitError | Too fast. error.scope === 'conversation' means the whole conversation is at its send cap, not just this user. |
import { LivqenoSlowModeError, LivqenoModerationError } from '@livqeno/chat';
try {
await chat.sendMessage({ text });
} catch (error) {
if (error instanceof LivqenoSlowModeError) startCountdown(error.retryAfterSeconds ?? 0);
else if (error instanceof LivqenoModerationError && error.code === 'MUTED') showMuted();
}The same codes come back from REST calls and WebSocket frames. On Flutter
they're LivqenoChatErrorCode.muted, .banned, .blocked, .slowMode and
.messageBlocked on LivqenoChatException.
Common errors
| Problem | Cause | Fix |
|---|---|---|
| A moderator's delete fails with a permission error | Their role is MEMBER, or their token's scopes left out chat:moderate. | Add them again with role: 'MODERATOR', and check the token's scopes. |
| A demoted moderator can still see the delete button | Your UI didn't update. The server already refuses their deletes. | Refresh the user's role in your UI. |