Systems
ModerationSystem
Warnings, mutes, kicks and bans that follow players across servers, roles and permissions, escalation ladders, reports, chat moderation, Roblox violation reports, commands, an audit log and server access control.
Getting it
local ModerationSystem = Engine:GetSystem("ModerationSystem")Overview
ModerationSystem is a complete moderation toolkit:
- Sanctions: warnings, mutes, kicks, bans, silent violations and your own kinds, with durations, reasons and private notes. Every sanction is saved in the player's record, applies in every server, and comes back when the player rejoins.
- Roles and permissions with ranks, so a moderator can never act on someone ranked above them.
- Escalation ladders that turn repeated offences into stronger sanctions automatically.
- Reports from players, with chat logs attached and a cross-server queue of open reports.
- Chat moderation through TextChatService: mutes, filters, spam and caps checks, slow mode and chat locks.
- Roblox violations: violations Roblox reports in your experience are recorded and count toward your escalation ladders.
- Commands such as
/ban,/muteand/warn, usable from the chat, from code and from the client. - An audit log of every action, kept per day in a DataStore and optionally posted to Discord.
- Server access: lock a server, run the experience in allowlist mode, or keep out brand new accounts.
It works out of the box. The experience owner (or, for group games, members at DeveloperMinGroupRank or above in PlayerService) automatically gets the Owner role, and the built-in commands are registered in the chat.
local ModerationSystem = Engine:GetSystem("ModerationSystem")
-- Give your staff roles
ModerationSystem:DefineRole("Staff", { Rank = 60, Inherits = { "Moderator" }, GroupRoles = { [1234567] = { "Moderator" } } })
-- Ban from code, for example from an anti-cheat check
ModerationSystem:Ban(player, { Reason = "Exploiting", PrivateReason = "Speed 250 for 4s", Duration = 7 * 86400 })
-- Turn repeated offences into stronger sanctions
ModerationSystem:DefineLadder("Default", {
Steps = {
{ Points = 3, Kind = "Mute", Duration = 600, Reason = "Repeated rule breaking" },
{ Points = 6, Kind = "Kick", Reason = "Repeated rule breaking" },
{ Points = 10, Kind = "Ban", Duration = 86400, Reason = "Repeated rule breaking" },
},
})local ModerationSystem = Engine:GetSystem("ModerationSystem")
reportButton.Activated:Connect(function()
ModerationSystem:ReportAsync(selectedPlayer, reasonBox.Text, { Category = "Harassment" })
:andThen(function()
showToast("Thanks, a moderator will look at it.")
end)
:catch(showToast)
end)
-- Show staff tools only to staff
adminPanel.Visible = ModerationSystem:Can("Commands")
ModerationSystem.StateChanged:Connect(function()
adminPanel.Visible = ModerationSystem:Can("Commands")
end)Who acts: By and permissions
Most methods take an options table with By: the Player or UserId performing the action. Leave it out for actions by your own code (the system, UserId 0), which are always allowed.
When By is a player and EnforcePermissions is on, the call checks that:
- The actor has the permission for the action (for example
Ban). - The actor is not targeting themselves.
- With
RequireHigherRank, the target's rank is lower than the actor's. - A target with the
Immunepermission can only be targeted by someone ranked above them.
If a check fails, the synchronous method returns nil (or false) plus a reason: "PermissionDenied", "CannotTargetSelf", "TargetOutranks", "TargetImmune" or "RoleOutranks". The Async version rejects with that reason. Denied fires either way. Pass Force = true to skip the checks.
-- A moderator's ban button in your own admin panel
local sanction, denied = ModerationSystem:Ban(target, { By = moderator, Reason = reason, Duration = duration })
if not sanction then
warn("Not allowed:", denied)
endSanctions
Built-in kinds
| Kind | Lasts | What happens |
|---|---|---|
Violation | Instant | Recorded silently, with points. Used for blocked chat messages and your own checks. |
Warn | Instant | The player sees the warning in chat. |
Mute | DefaultMuteSeconds unless given | The player cannot chat. With Shadow = true they still see their own messages, but nobody else does. |
Kick | Instant | The player is kicked, in whichever server they are in. |
Ban | Permanent unless given | The player is kicked and cannot join. With UseRobloxBans, Roblox's ban API (Players:BanAsync) also bans them across the universe. |
Several warnings and violations can exist at once. For other kinds, a new sanction replaces the previous active one of the same kind.
Applying
| Method | Description |
|---|---|
:Warn(target, options?) | |
:Mute(target, options?) | |
:Kick(target, options?) | |
:Ban(target, options?) | |
:Violation(target, options?) | |
:Apply(kind, target, options?) | Any kind, including your own. |
Each returns (Sanction?, reason?) right away and has an Async version (:BanAsync, :ApplyAsync, ...) that resolves with the sanction once the record is saved. target is a Player or a UserId, so players who are offline or in another server can be sanctioned too. Passing a string instead of options is a shortcut for { Reason = text }.
| Option | Description |
|---|---|
Duration | Seconds; leave out for permanent (or the kind's default). |
Reason | Shown to the player (up to MaxDisplayReasonLength characters). |
PrivateReason | Only for staff and the Roblox ban's private reason. |
By | The actor. |
Points | Escalation points; defaults to DefaultPoints for the kind. |
Data | Extra information saved with the sanction. |
Shadow | Do not tell the player (shadow mute). |
Silent | Apply without showing a notice. |
Force | Skip permission checks. |
ExcludeAltAccounts, ApplyDeviceBlock, ApplyToUniverse | Override the Roblox ban settings for this ban. |
Lifting
| Method | Description |
|---|---|
:Unban(target, options?), :Unmute(target, options?) | |
:Lift(target, kindOrId, options?) | Lifts every active sanction of a kind, or one sanction by its id. |
They return ({ Sanction }, reason?) with the lifted sanctions, and have Async versions. Options: By, Reason, Force, Silent. Unbanning also removes the Roblox ban. Sanctions with a duration expire on their own; Expired fires when they do.
Checking
| Method | Description |
|---|---|
:IsBanned(target), :IsBannedAsync(target) | |
:IsMuted(target) | |
:GetActiveSanction(target, kind), :GetActiveSanctions(target, kind?) | |
:GetSanctionHistory(target, kind?, limit?), :GetSanctionHistoryAsync(...) | Newest first. |
:FindSanction(target, sanctionId) | |
:GetPoints(target, kinds?) | Escalation points that still count (see PointsExpireDays). |
:GetBanHistoryAsync(target) | The player's history from Roblox's ban API. |
The methods without Async use the records loaded in this server (players in the server). Use the Async versions for anyone else.
Custom kinds
ModerationSystem:DefineSanction("Jail", {
DefaultDuration = 300,
Points = 2,
Apply = function(player, sanction)
if player then sendToJail(player) end
end,
Lift = function(player, sanction)
if player then releaseFromJail(player) end
end,
})
ModerationSystem:Apply("Jail", player, { By = moderator, Reason = "Spawn killing" })| Option | Description |
|---|---|
Points | Default escalation points. |
Instant | Recorded but never active (like a warning). |
Notify | Show the player a notice (default true). |
Stackable | Several can be active at once. |
DefaultDuration, DefaultReason | |
Apply(player?, sanction) | Called when the sanction starts, and again when the player rejoins while it is active. |
Lift(player?, sanction) | Called when it is lifted or expires. |
:GetSanctionKind(name), :FindSanctionKind(name) and :GetSanctionKinds() look kinds up. Custom kinds need the Sanction permission.
Events
| Event | Fires when |
|---|---|
Sanctioned: Signal<(sanction, player?)> | Any sanction was applied. |
Warned, Muted, Kicked, Banned | The same, per kind. |
Lifted, Unmuted, Unbanned: Signal<(sanction, player?)> | |
Expired: Signal<(sanction, player?)> | |
RobloxBanFailed: Signal<(userId, error)> | A Roblox ban or unban call failed; the record still applies. |
player is the Player when they are in this server.
With UseRobloxBans on, every ban calls Players:BanAsync. When banning is turned off in Game Settings, Roblox refuses that call and RobloxBanFailed fires, but the moderation record still enforces the ban: the player is kicked when they join (KickBannedOnJoin).
Records
Every player has a record in the RecordStoreName DataStore with their sanctions, notes, roles, reports and escalation state. Records of players in the server are loaded on join (LoadRecordOnJoin).
| Method | Description |
|---|---|
:GetRecord(target) | The loaded record, or nil. |
:GetRecordAsync(target) | Loads any player's record. |
:HasRecord(target) | Whether it is loaded here. |
:EraseRecordAsync(target) | Clears everything in the record, such as for a data deletion request. |
RecordLoaded: Signal<(player, record)> |
Notes
Private notes for staff.
| Method | Description |
|---|---|
:AddNote(target, text, options?), :AddNoteAsync(...) | |
:RemoveNoteAsync(target, noteId, options?) | |
:GetNotes(target), :GetNotesAsync(target) | |
NoteAdded: Signal<(userId, note)> |
Roles and permissions
DefineRole
ModerationSystem:DefineRole(name: string, options: RoleOptions?): Role| Option | Description |
|---|---|
Rank | Higher ranks outrank lower ones. |
Permissions | Permission names, or "*" for all. |
Inherits | Roles whose permissions this role also gets. |
UserIds | Players who always have the role. |
GroupRoles | { [groupId] = { roleId or role name, ... } }: anyone holding one of those group roles has the role. |
GroupRanks | { [groupId] = minimumRank }: group members whose highest role is at that rank or higher have the role. |
GamePasses | Owners of any of these passes have the role. |
Predicate | function(player) -> boolean for your own rule. |
DisplayName, Color |
Roblox groups can give a member several roles, and a rank no longer says where a role sits, so GroupRoles is the reliable way to map your group's staff. GroupRanks keeps working. Both can also be set on the roles in the Roles setting.
The roles Owner, Admin, Moderator and Helper come from the Roles setting. Players can also be granted roles, temporarily or permanently, and those grants are saved in their record.
| Method | Description |
|---|---|
:GrantRole(target, role, options?), :GrantRoleAsync(...) | Options: By, Duration, Reason, Force. An actor cannot grant a role ranked at or above their own. |
:RevokeRole(target, role, options?), :RevokeRoleAsync(...) | |
:GetRolesOf(target), :GetRolesOfAsync(target) | |
:HasRole(target, role), :GetRank(target), :GetHighestRole(target) | |
:GetPermissions(target), :Can(target, permission) | |
:CanTarget(actor, target): (boolean, reason?) | |
:IsOwner(target) | |
:GetRole(name), :FindRole(name), :GetRoles(), :RemoveRole(name) | |
RoleGranted, RoleRevoked: Signal<(userId, role, player?)>, RolesChanged: Signal<(player, roles)> | |
Denied: Signal<(actorId, permission, targetId?, reason)> |
Permissions
ModerationSystem.Permissions lists them: Warn, Mute, Unmute, Kick, Ban, Unban, Lift, Sanction (custom kinds), Notes, Reports, Records, Roles, Audit, Chat (slow mode and chat locks), ChatBypass (skip chat filters, slow mode and chat locks), Server (server lock and allowlist, and joining locked servers), Commands, Immune, and "*" for everything.
Escalation ladders
Every sanction adds points (DefaultPoints: Violation 1, Warn 1, Mute 2, Kick 3, Ban 5). A ladder applies a stronger sanction when a player's points reach a step.
ModerationSystem:DefineLadder(name: string, options: LadderOptions): Ladder| Option | Description |
|---|---|
Steps | { Points, Kind, Duration?, Reason? } in increasing order of points. |
Kinds | Only count points from these kinds. |
Window | Only count points from the last N seconds; otherwise points count for PointsExpireDays. |
Repeat | Apply the last step again every time a new sanction arrives while the player is still at it. |
Each step fires once until the player reaches the next one. Sanctions applied by a ladder add no points themselves and fire Escalated: Signal<(sanction, trigger)>. :GetLadder, :FindLadder, :GetLadders, :RemoveLadder and :ResetLadder(target, name?) manage them. Ladders can also be defined in the Ladders setting.
Reports
| Method | Description |
|---|---|
:FileReport(reporter, target, reason, options?), :FileReportAsync(...) | Options: Category, Evidence (any JSON-friendly data), AttachChatLog. |
:ReviewReport(reportId, status, options?), :ReviewReportAsync(...) | Status "Open", "Reviewing", "Resolved" or "Dismissed"; options By, Resolution, Force. |
:GetReport(id), :GetReportAsync(id) | |
:GetReports(filter?) | Reports in this server's memory; filter by Target, Reporter, Status, Since, Limit. |
:GetOpenReportsAsync(count?) | Open reports from every server (the MemoryStore queue). |
:GetReportsAgainstAsync(target) | The reports in a player's record. |
Filing fails with "ReportsDisabled", "InvalidTarget", "SelfReport", "InvalidReason", "ReasonTooLong", "EvidenceTooLarge", "Cooldown" or "Duplicate" (ModerationSystem.ReportErrors). Each report carries the target's recent chat messages when AttachChatLog is on.
When ReportThreshold different players report someone within ReportWindow, ReportThresholdReached: Signal<(userId, count, player?)> fires, and ReportThresholdKind (if set) is applied automatically. ReportFiled: Signal<(report, player?)> and ReportReviewed: Signal<report> fire for every report.
Chat moderation
With ChatModeration on, the system hooks TextChatService. Each message passes these checks in order:
- Muted: muted players cannot chat, whatever their permissions.
- ChatLocked and SlowMode.
- The built-in filter: TooLong (
ChatMaxLength), Spam (ChatMaxMessagesPerWindow), Repeat (ChatMaxRepeats), Link (ChatBlockLinks), Blocklist (ChatBlocklist) and Caps (ChatMaxCapsRatio). - Your own filters.
Players with ChatBypass skip steps 2 to 4.
A blocked message is not delivered, the player is told why, and (for the reasons in ChatViolationReasons) a Violation with ChatViolationPoints is recorded. Roblox's own text filtering still applies on top.
Important
ModerationSystem runs these checks from ShouldDeliverCallback, which it sets on every TextChannel, including channels added later. Roblox does not let a script read a callback, so a ShouldDeliverCallback your game sets on a channel itself is not chained with ModerationSystem's: whichever is set last wins. If yours is set last, slow mode, chat locks and the chat filters no longer apply in that channel. Put your own checks in :AddChatFilter, or set ChatModeration to false to keep your own callback.
| Method | Description |
|---|---|
:AddChatFilter(name, handler, priority?) | handler(ctx) -> (blocked?, reason?), where ctx has Player, UserId, Text, Channel and Message. Lower priorities run first. |
:RemoveChatFilter(name), :GetChatFilters() | |
:CheckText(text): Verdict | Runs the built-in filter on any text: { Blocked, Reason?, Filter? }. Useful for pet names or signs. |
:FilterTextAsync(text, from, options?) | Roblox text filtering. Options: For (a Player, a UserId or "Broadcast"), Context. |
:LockChat(locked, options?) | Only players with ChatBypass can chat while locked. |
:SetSlowMode(seconds, channel?, options?), :GetSlowMode(channel?) | |
:GetChatLog(target) | The player's recent messages (ChatLogSize). |
ChatBlocked: Signal<(player, text, reason, filter?)>, ChatMessage: Signal<(player, text, channel)> |
ModerationSystem:AddChatFilter("NoTrading", function(ctx)
if string.find(string.lower(ctx.Text), "trade me") then
return true, "Use the trading hub."
end
return false
end)Notices
:Notify(target, text, kind?) shows a message to a player (in any server); :NotifyAll(text, kind?) to everyone here. With NoticesInChat, the client shows notices as system messages in the chat.
Roblox violations
Roblox reports the violations its own moderation finds in your experience. When ModerationSystem loads, it binds one violation processor for them with ModerationService:BindViolationProcessor, unless RobloxViolations is off. Every report:
- fires
RobloxViolation: Signal<(violation, player?)>; - is written to the audit log as
RobloxViolation; - records a
Violationsanction worthRobloxViolationPointson the reported user, so your escalation ladders act on Roblox's reports the same way they act on chat violations.
The sanction's reason is Roblox: followed by the report's category (or its first label), or DefaultViolationReason when the report names neither. With AcknowledgeRobloxViolations on, every handled report is acknowledged, so Roblox does not deliver it again.
violation carries Id, UserId, Category, Labels (a list of names), At and the untouched Report. player is the Player when the user is in this server.
Choosing the consequence
Set the callback OnRobloxViolation to decide per report:
ModerationSystem.OnRobloxViolation = function(violation, player)
if player then
return { Kind = "Mute", Duration = 600, Reason = "Roblox reported a violation" }
end
return nil -- not in this server: the default Violation sanction
end| Return | What happens |
|---|---|
nil | The default: a sanction of the kind in RobloxViolationKind (Violation). |
A sanction kind, such as "Kick" | That sanction instead. |
{ Kind?, Duration?, Reason?, PrivateReason?, Points?, Shadow?, Silent? } | A sanction with these options. |
false | Nothing is recorded or logged, and the report is not acknowledged. |
With RobloxViolationKind set to an empty string, the reports your callback leaves to the default are only logged, without a sanction.
If your game binds its own violation processor too, set RobloxViolationPriority, or AcknowledgeRobloxViolations = false, so both see the report. ModerationSystem.IsViolationProcessorBound tells whether the processor is bound.
Testing in Studio
ModerationSystem:MockViolationAsync(report: { [string]: any }): PromiseSends a test report through ModerationService:MockViolationCallbackAsync, as if Roblox reported it, so you can try your violation handling in Studio.
Commands
Commands run from the chat (with CommandPrefix, default /), from code with :ExecuteAsync, and from the client. Players only see and can run the commands their permissions allow.
Built-in commands
| Command | Permission | Usage |
|---|---|---|
ban (b) | Ban | ban <user> [duration] [reason] |
unban | Unban | unban <user> [reason] |
kick (k) | Kick | kick <target> [reason] |
mute (m) | Mute | mute <user> [duration] [reason] |
shadowmute | Mute | shadowmute <user> [duration] [reason] |
unmute | Unmute | unmute <user> [reason] |
warn (w) | Warn | warn <user> [reason] |
lift | Lift | lift <user> <case> [reason] |
record (history) | Records | record <user> |
warnings | Records | warnings <user> |
note, notes | Notes | note <user> <text>, notes <user> |
role (grant), unrole (revoke) | Roles | role <user> <role> [duration], unrole <user> <role> |
roles | roles [target] | |
lockserver, unlockserver | Server | lockserver [reason] |
kickall | Server | kickall [reason] |
slowmode | Chat | slowmode <seconds> [channel] |
lockchat, unlockchat | Chat | |
reports, resolve | Reports | reports [target], resolve <report> [resolution] |
report | report <target> <reason> | |
commands (help) | Lists the commands you can use. |
<user> accepts a player in the server, a username (also offline players), @username or a UserId. Durations are written like 30m, 2h, 7d, 1w, 3mo, 1y, combinations such as 1d12h, or perm. me targets yourself. Turn the built-ins off with BuiltInCommands = false.
RegisterCommand
ModerationSystem:RegisterCommand("heal", {
Permission = "Commands",
Description = "Heal a player.",
Args = {
{ Name = "target", Type = "Target" },
{ Name = "amount", Type = "Number", Optional = true, Default = 100 },
},
Run = function(ctx, args)
args.target.Character.Humanoid.Health += args.amount
return string.format("Healed %s.", args.target.Name)
end,
})| Option | Description |
|---|---|
Run(ctx, args) -> string? | ctx has Actor, ActorId, Source ("Chat", "Api", "Client"), Line, Command and Reply(text). A returned string is sent back as the reply. |
Args | { Name, Type, Optional?, Default?, Description? }. Types: Target (a player in this server), User (a UserId, online or not), Duration, Number, Integer, String (one word), Text (the rest of the line), Boolean, Role, Kind. |
Aliases, Permission, Description | |
Hidden | Leave out of command lists. |
Chat | Register it as a TextChatCommand (default true). |
| Method | Description |
|---|---|
:ExecuteAsync(actor, line, options?) | Runs a command line. Options: Source, Reply. Rejects with a readable message. |
:UnregisterCommand(name), :GetCommand(name), :FindCommand(name), :GetCommands() | |
:FindPlayer(query, actor?) | Finds a player in this server by UserId, name or display name (or a unique prefix of one). |
:ResolveUserIdAsync(playerOrIdOrName) | |
:ParseDuration(text): (boolean, seconds?), :FormatDuration(seconds?) | nil seconds means permanent. |
CommandExecuted: Signal<(player?, command, args, reply?)> |
Failures use ModerationSystem.CommandErrors: UnknownCommand, PermissionDenied, MissingArgument, InvalidArgument, PlayerNotFound, UserNotFound, AmbiguousPlayer, CommandFailed.
Audit log
Every action (sanctions, lifts, roles, notes, reports, commands, locks) is logged with who did it, to whom, why and when.
| Method | Description |
|---|---|
:Log(action, { Actor, Target?, Reason?, Data? }) | Add your own entry. |
:GetAuditLog(filter?) | Entries in this server's memory (AuditBufferSize). |
:GetAuditLogAsync(filter?) | Entries from the DataStore. Filter by Action, Actor, Target, Since, Until, Limit. |
:FlushAuditAsync() | Write pending entries now. |
Logged: Signal<entry>, AuditFlushFailed: Signal<(day, error)> |
Entries also go to StatisticsService as Moderation.<Action> custom events (LogToStatistics), and to a Discord webhook when WebhookName names a StatisticsService webhook.
Server access
| Method | Description |
|---|---|
:LockServer(locked, options?), :IsServerLocked() | A locked server kicks joining players without the Server permission. |
:KickAll({ By?, Reason?, Exempt? }) | Kicks everyone without the Exempt permission (default Server); returns the count. |
:SetAccessModeAsync("Open" | "Allowlist", options?), :GetAccessMode() | Universe-wide. In Allowlist mode only listed players and staff with Server can join. |
:AllowAsync(target, options?), :DisallowAsync(target, options?), :IsAllowed(target), :GetAllowlist() | |
:LoadAccessAsync() | Reloads the access document. |
ServerLockChanged: Signal<(locked, reason?)>, AccessChanged: Signal<mode> |
With MinAccountAgeDays, accounts younger than that (and without a role) are kicked on join.
Properties
| Property | Description |
|---|---|
ServerLocked, ChatLocked, SlowMode | Settable; the same as the matching methods. |
LogToStatistics | Settable. |
OnRobloxViolation | Settable callback; see Roblox violations. |
AccessMode, CommandPrefix, RecordStoreName, IsChatHooked, IsViolationProcessorBound, RoleCount, LoadedRecordCount | Read-only. |
Cross-server
With CrossServerSync, sanctions, lifts, kicks, role changes, notices and reports are sent to every other server through MessagingService. Banning someone who is playing in another server kicks them there; muting them applies right away.
Client
| Member | Description |
|---|---|
:GetState(), :WaitForStateAsync(timeout?), :RefreshStateAsync() | The local player's moderation state. |
:GetRoles(), :HasRole(role), :GetRank(), :GetPermissions(), :Can(permission) | |
:IsMuted(), :GetSanctions(), :GetActiveSanction(kind) | Active sanctions (shadow sanctions are never sent). |
:GetCommands() | Commands this player may run, with usage. |
:ReportAsync(target, reason, { Category?, Evidence? }?) | Resolves with the report id. |
:ExecuteAsync(line) | Runs a command on the server; resolves with the reply. |
StateChanged, Notice, Warned, Muted, Unmuted, Sanctioned, RolesChanged | Signals. |
ChatBlocked: Signal<(text, reason)>, CommandReply: Signal<text> | |
IsReady, ChatLocked, SlowMode, ServerLocked, CommandPrefix | Read-only. |
Use it to show staff panels, a mute timer or a report button. Every request is checked again on the server.
Configuration
DefaultConfiguration/Systems/ModerationSystem85 settings| Setting | Default | What it does |
|---|---|---|
| RecordStoreName | "Engine.Moderation" | DataStore every player's moderation record lives in. |
| AuditStoreName | "Engine.Moderation.Audit" | DataStore the daily audit log pages live in. |
| ReportStoreName | "Engine.Moderation.Reports" | DataStore filed reports live in, one key per report. |
| ReportQueueName | "Engine.Moderation.OpenReports" | MemoryStore sorted map that lists open reports across servers. |
| StateDocumentName | "Engine.Moderation.State" | Document the universe-wide access mode and allowlist are kept in. |
| TopicName | "Engine.Moderation" | MessagingService topic sanctions, lifts and role changes travel between servers on. |
| CrossServerSync | true | Publish sanctions, lifts, kicks and role changes to every other server. |
| LoadRecordOnJoin | true | Load a joining player's record so active sanctions and roles apply. |
| UseRobloxBans | true | Also ban and unban through Players:BanAsync so Roblox evicts the player everywhere. |
| BanApplyToUniverse | true | Roblox bans cover the whole universe instead of only this place. |
| BanExcludeAltAccounts | false | Roblox bans skip the player's suspected alternate accounts. |
| BanApplyDeviceBlock | false | Roblox bans also block the banned device for 24 hours. |
| KickBannedOnJoin | true | Kick a joining player whose record still holds an active ban. |
| DefaultBanReason | "You have been banned from this experience." | Reason a ban shows when the call passes none. |
| DefaultKickReason | "You have been kicked from this experience." | Reason a kick shows when the call passes none. |
| DefaultMuteReason | "You have been muted." | Reason a mute shows when the call passes none. |
| DefaultWarnReason | "You have received a warning." | Reason a warning shows when the call passes none. |
| DefaultMuteSeconds | 600 | Seconds a mute lasts when the call passes no Duration. |
| MaxDisplayReasonLength | 400 | Longest reason shown to a player, in characters. |
| MaxPrivateReasonLength | 1000 | Longest private note attached to a sanction, in characters. |
| IncludeCaseIdInKick | true | Append the sanction id to a kick or ban message so the player can quote it. |
| KickMessageFormat | "%s\n%s" | string.format pattern joining the reason and the duration line of a kick or ban message. |
| OwnerRole | "Owner" | Role the experience owner and group owners get automatically. |
| Roles | { Owner = { Rank = 1000, Permissions = { "*" } }, Admin = { Rank = 100, Permissions = { "*" } }, Moderator = { Rank = 50, Permissions = { "Warn", "Mute", "Unmute", "Kick", "Ban", "Unban", "Lift", "Sanction", "Notes", "Reports", "Records", "Chat", "ChatBypass", "Commands" } }, Helper = { Rank = 10, Permissions = { "Warn", "Mute", "Unmute", "Notes", "Reports", "Records", "ChatBypass", "Commands" } }, } | Roles defined at boot, same shape as a :DefineRole call. |
| RequireHigherRank | true | A moderator may only act on players whose rank is below their own. |
| EnforcePermissions | true | Check the actor's permissions and rank on every call that names a By. |
| DefaultPoints | { Violation = 1, Warn = 1, Mute = 2, Kick = 3, Ban = 5 } | Escalation points a sanction of each kind adds when the call passes none. |
| PointsExpireDays | 30 | Days a sanction's points keep counting toward escalation ladders. |
| Ladders | {} | Escalation ladders defined at boot, same shape as a :DefineLadder call. |
| MaxHistoryPerRecord | 100 | Sanctions a record keeps before the oldest inactive ones are dropped. |
| MaxNotesPerRecord | 50 | Notes a record keeps before the oldest are dropped. |
| ExpirySweepSeconds | 5 | Seconds between checks for expired sanctions on loaded records. |
| ChatModeration | true | Hook TextChatService so mutes, filters, slow mode and commands apply to the chat. |
| ChatBlocklist | {} | Words the chat filter blocks; an entry starting with "pattern:" is a Lua pattern. |
| ChatBlockLinks | true | Block chat messages containing links or invites. |
| ChatMaxLength | 200 | Longest chat message the filter lets through; 0 disables. |
| ChatMaxMessagesPerWindow | 6 | Messages one player may send inside ChatWindowSeconds before Spam blocks them; 0 disables. |
| ChatWindowSeconds | 5 | Seconds the spam window spans. |
| ChatMaxRepeats | 3 | Times the same message may repeat before Repeat blocks it; 0 disables. |
| ChatMaxCapsRatio | 0.8 | Share of upper-case letters above which Caps blocks a message; 0 disables. |
| ChatMinCapsLength | 12 | Letters a message needs before the caps check applies. |
| ChatLogSize | 25 | Recent chat messages kept per player for reports and evidence. |
| MuteBlocksDelivery | true | Also drop a muted player's messages server-side so a shadow mute stays invisible to them. |
| ChatViolationPoints | 1 | Escalation points a blocked chat message records; 0 records nothing. |
| ChatViolationReasons | { "Blocklist", "Link", "Spam" } | Filter reasons that record a violation; an empty list counts every block. |
| SlowModeSeconds | 0 | Seconds between chat messages of one player at boot; 0 is off. |
| RobloxViolations | true | Bind a ModerationService violation processor so violations Roblox reports in this experience reach ModerationSystem. |
| RobloxViolationPriority | 0 | Priority of that processor among the experience's own violation processors. |
| RobloxViolationKind | "Violation" | Sanction kind a reported violation records when OnRobloxViolation decides nothing; empty records none. |
| RobloxViolationPoints | 1 | Escalation points a reported violation adds when it records a Violation. |
| AcknowledgeRobloxViolations | true | Acknowledge every handled violation so Roblox does not deliver it again. |
| DefaultViolationReason | "Roblox reported a violation." | Reason a reported violation records when the report names no category. |
| ReportsEnabled | true | Accept reports from :FileReport and from the client. |
| PersistReports | true | Write reports to the report DataStore and the open-report queue. |
| ReportCooldown | 30 | Seconds a player waits between two reports. |
| ReportDuplicateWindow | 600 | Seconds the same reporter cannot report the same target again. |
| ReportThreshold | 3 | Distinct reporters inside ReportWindow that fire ReportThresholdReached; 0 disables. |
| ReportWindow | 900 | Seconds the report threshold looks back. |
| ReportThresholdKind | "" | Sanction kind applied automatically when the threshold is reached; empty applies none. |
| ReportThresholdDuration | 600 | Seconds that automatic sanction lasts. |
| MaxReportsPerRecord | 50 | Reports a record keeps before the oldest are dropped. |
| MaxReportReasonLength | 200 | Longest report reason, in characters. |
| MaxReportEvidenceBytes | 4096 | Largest JSON-encoded evidence a report may carry. |
| ReportQueueSeconds | 604800 | Seconds an open report stays listed in the cross-server queue. |
| ReportLocalLimit | 500 | Reports kept in this server's memory. |
| AttachChatLog | true | Attach the target's recent chat to a filed report. |
| AuditBufferSize | 500 | Audit entries kept in this server's memory. |
| PersistAudit | true | Write audit entries to the audit DataStore. |
| AuditFlushSeconds | 30 | Seconds pending audit entries are batched before one write. |
| AuditDayCapacity | 5000 | Audit entries one day's page keeps before the oldest are dropped. |
| LogToStatistics | true | Forward every audit entry to StatisticsService as a custom event. |
| WebhookName | "" | StatisticsService webhook that receives an embed per audit entry; empty sends none. |
| CommandPrefix | "/" | Prefix a chat message needs to run a command. |
| BuiltInCommands | true | Register the built-in ban, kick, mute, warn, note, role and server commands. |
| CommandsInChat | true | Register every command as a TextChatCommand so it runs from the chat. |
| CommandsFromClient | true | Let the client half run commands through the network. |
| MinAccountAgeDays | 0 | Kick a joining player whose account is younger than this many days; 0 disables. |
| AccountAgeKickReason | "Your account is too new to join this experience." | Reason shown to a player kicked for account age. |
| ServerLockKickReason | "This server is locked." | Reason shown to a player kicked by the server lock. |
| AccessKickReason | "You are not on the allowlist for this experience." | Reason shown to a player kicked by the allowlist. |
| ReplicateToClient | true | Push roles, permissions and active sanctions to the owning client. |
| NoticesInChat | true | The client shows warnings, mutes and command replies as system chat messages. |
| ClientRequestRatePerSec | 1 | Per-player rate limit on client report, command and state requests. |
| ClientRequestBurst | 3 | Burst that request limiter allows. |
| ClientTimeout | 10 | Seconds the client waits for its moderation state before rejecting. |
Types
export type Target = Player | number
export type Actor = Player | number | nil
export type Sanction
export type SanctionKind
export type SanctionKindOptions
export type SanctionOptions
export type LiftOptions
export type Record
export type Note
export type RoleGrant
export type Role
export type RoleOptions
export type GrantOptions
export type Ladder
export type LadderOptions
export type LadderStep = { Points: number, Kind: string, Duration: number?, Reason: string? }
export type EscalationTrigger
export type Report
export type ReportStatus = "Open" | "Reviewing" | "Resolved" | "Dismissed"
export type ReportFilter
export type AuditEntry
export type AuditFilter
export type ChatLogEntry
export type ChatFilterContext
export type ChatVerdict = { Blocked: boolean, Reason: string?, Filter: string? }
export type Command
export type CommandOptions
export type CommandContext
export type CommandArg
export type AccessMode = "Open" | "Allowlist"
export type RobloxViolation
export type RobloxViolationDecision
export type ClientState
export type PublicSanction
export type BanHistoryEntry