Skip to content
Engine

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.

SystemServerClient

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, /mute and /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.

Server
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" },
	},
})
Client
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:

  1. The actor has the permission for the action (for example Ban).
  2. The actor is not targeting themselves.
  3. With RequireHigherRank, the target's rank is lower than the actor's.
  4. A target with the Immune permission 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)
end

Sanctions

Built-in kinds

KindLastsWhat happens
ViolationInstantRecorded silently, with points. Used for blocked chat messages and your own checks.
WarnInstantThe player sees the warning in chat.
MuteDefaultMuteSeconds unless givenThe player cannot chat. With Shadow = true they still see their own messages, but nobody else does.
KickInstantThe player is kicked, in whichever server they are in.
BanPermanent unless givenThe 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

MethodDescription
: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 }.

OptionDescription
DurationSeconds; leave out for permanent (or the kind's default).
ReasonShown to the player (up to MaxDisplayReasonLength characters).
PrivateReasonOnly for staff and the Roblox ban's private reason.
ByThe actor.
PointsEscalation points; defaults to DefaultPoints for the kind.
DataExtra information saved with the sanction.
ShadowDo not tell the player (shadow mute).
SilentApply without showing a notice.
ForceSkip permission checks.
ExcludeAltAccounts, ApplyDeviceBlock, ApplyToUniverseOverride the Roblox ban settings for this ban.

Lifting

MethodDescription
: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

MethodDescription
: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" })
OptionDescription
PointsDefault escalation points.
InstantRecorded but never active (like a warning).
NotifyShow the player a notice (default true).
StackableSeveral 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

EventFires when
Sanctioned: Signal<(sanction, player?)>Any sanction was applied.
Warned, Muted, Kicked, BannedThe 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).

MethodDescription
: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.

MethodDescription
: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
OptionDescription
RankHigher ranks outrank lower ones.
PermissionsPermission names, or "*" for all.
InheritsRoles whose permissions this role also gets.
UserIdsPlayers 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.
GamePassesOwners of any of these passes have the role.
Predicatefunction(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.

MethodDescription
: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
OptionDescription
Steps{ Points, Kind, Duration?, Reason? } in increasing order of points.
KindsOnly count points from these kinds.
WindowOnly count points from the last N seconds; otherwise points count for PointsExpireDays.
RepeatApply 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

MethodDescription
: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:

  1. Muted: muted players cannot chat, whatever their permissions.
  2. ChatLocked and SlowMode.
  3. The built-in filter: TooLong (ChatMaxLength), Spam (ChatMaxMessagesPerWindow), Repeat (ChatMaxRepeats), Link (ChatBlockLinks), Blocklist (ChatBlocklist) and Caps (ChatMaxCapsRatio).
  4. 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.

MethodDescription
: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): VerdictRuns 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:

  1. fires RobloxViolation: Signal<(violation, player?)>;
  2. is written to the audit log as RobloxViolation;
  3. records a Violation sanction worth RobloxViolationPoints on 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
ReturnWhat happens
nilThe 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.
falseNothing 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 }): Promise

Sends 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

CommandPermissionUsage
ban (b)Banban <user> [duration] [reason]
unbanUnbanunban <user> [reason]
kick (k)Kickkick <target> [reason]
mute (m)Mutemute <user> [duration] [reason]
shadowmuteMuteshadowmute <user> [duration] [reason]
unmuteUnmuteunmute <user> [reason]
warn (w)Warnwarn <user> [reason]
liftLiftlift <user> <case> [reason]
record (history)Recordsrecord <user>
warningsRecordswarnings <user>
note, notesNotesnote <user> <text>, notes <user>
role (grant), unrole (revoke)Rolesrole <user> <role> [duration], unrole <user> <role>
rolesroles [target]
lockserver, unlockserverServerlockserver [reason]
kickallServerkickall [reason]
slowmodeChatslowmode <seconds> [channel]
lockchat, unlockchatChat
reports, resolveReportsreports [target], resolve <report> [resolution]
reportreport <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,
})
OptionDescription
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
HiddenLeave out of command lists.
ChatRegister it as a TextChatCommand (default true).
MethodDescription
: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.

MethodDescription
: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

MethodDescription
: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

PropertyDescription
ServerLocked, ChatLocked, SlowModeSettable; the same as the matching methods.
LogToStatisticsSettable.
OnRobloxViolationSettable callback; see Roblox violations.
AccessMode, CommandPrefix, RecordStoreName, IsChatHooked, IsViolationProcessorBound, RoleCount, LoadedRecordCountRead-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

MemberDescription
: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, RolesChangedSignals.
ChatBlocked: Signal<(text, reason)>, CommandReply: Signal<text>
IsReady, ChatLocked, SlowMode, ServerLocked, CommandPrefixRead-only.

Use it to show staff panels, a mute timer or a report button. Every request is checked again on the server.

Configuration

Defaults from DefaultConfiguration/Systems/ModerationSystem85 settings
SettingDefaultWhat 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.
CrossServerSynctruePublish sanctions, lifts, kicks and role changes to every other server.
LoadRecordOnJointrueLoad a joining player's record so active sanctions and roles apply.
UseRobloxBanstrueAlso ban and unban through Players:BanAsync so Roblox evicts the player everywhere.
BanApplyToUniversetrueRoblox bans cover the whole universe instead of only this place.
BanExcludeAltAccountsfalseRoblox bans skip the player's suspected alternate accounts.
BanApplyDeviceBlockfalseRoblox bans also block the banned device for 24 hours.
KickBannedOnJointrueKick 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.
DefaultMuteSeconds600Seconds a mute lasts when the call passes no Duration.
MaxDisplayReasonLength400Longest reason shown to a player, in characters.
MaxPrivateReasonLength1000Longest private note attached to a sanction, in characters.
IncludeCaseIdInKicktrueAppend 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.
RequireHigherRanktrueA moderator may only act on players whose rank is below their own.
EnforcePermissionstrueCheck 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.
PointsExpireDays30Days a sanction's points keep counting toward escalation ladders.
Ladders{}Escalation ladders defined at boot, same shape as a :DefineLadder call.
MaxHistoryPerRecord100Sanctions a record keeps before the oldest inactive ones are dropped.
MaxNotesPerRecord50Notes a record keeps before the oldest are dropped.
ExpirySweepSeconds5Seconds between checks for expired sanctions on loaded records.
ChatModerationtrueHook 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.
ChatBlockLinkstrueBlock chat messages containing links or invites.
ChatMaxLength200Longest chat message the filter lets through; 0 disables.
ChatMaxMessagesPerWindow6Messages one player may send inside ChatWindowSeconds before Spam blocks them; 0 disables.
ChatWindowSeconds5Seconds the spam window spans.
ChatMaxRepeats3Times the same message may repeat before Repeat blocks it; 0 disables.
ChatMaxCapsRatio0.8Share of upper-case letters above which Caps blocks a message; 0 disables.
ChatMinCapsLength12Letters a message needs before the caps check applies.
ChatLogSize25Recent chat messages kept per player for reports and evidence.
MuteBlocksDeliverytrueAlso drop a muted player's messages server-side so a shadow mute stays invisible to them.
ChatViolationPoints1Escalation 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.
SlowModeSeconds0Seconds between chat messages of one player at boot; 0 is off.
RobloxViolationstrueBind a ModerationService violation processor so violations Roblox reports in this experience reach ModerationSystem.
RobloxViolationPriority0Priority 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.
RobloxViolationPoints1Escalation points a reported violation adds when it records a Violation.
AcknowledgeRobloxViolationstrueAcknowledge 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.
ReportsEnabledtrueAccept reports from :FileReport and from the client.
PersistReportstrueWrite reports to the report DataStore and the open-report queue.
ReportCooldown30Seconds a player waits between two reports.
ReportDuplicateWindow600Seconds the same reporter cannot report the same target again.
ReportThreshold3Distinct reporters inside ReportWindow that fire ReportThresholdReached; 0 disables.
ReportWindow900Seconds the report threshold looks back.
ReportThresholdKind""Sanction kind applied automatically when the threshold is reached; empty applies none.
ReportThresholdDuration600Seconds that automatic sanction lasts.
MaxReportsPerRecord50Reports a record keeps before the oldest are dropped.
MaxReportReasonLength200Longest report reason, in characters.
MaxReportEvidenceBytes4096Largest JSON-encoded evidence a report may carry.
ReportQueueSeconds604800Seconds an open report stays listed in the cross-server queue.
ReportLocalLimit500Reports kept in this server's memory.
AttachChatLogtrueAttach the target's recent chat to a filed report.
AuditBufferSize500Audit entries kept in this server's memory.
PersistAudittrueWrite audit entries to the audit DataStore.
AuditFlushSeconds30Seconds pending audit entries are batched before one write.
AuditDayCapacity5000Audit entries one day's page keeps before the oldest are dropped.
LogToStatisticstrueForward 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.
BuiltInCommandstrueRegister the built-in ban, kick, mute, warn, note, role and server commands.
CommandsInChattrueRegister every command as a TextChatCommand so it runs from the chat.
CommandsFromClienttrueLet the client half run commands through the network.
MinAccountAgeDays0Kick 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.
ReplicateToClienttruePush roles, permissions and active sanctions to the owning client.
NoticesInChattrueThe client shows warnings, mutes and command replies as system chat messages.
ClientRequestRatePerSec1Per-player rate limit on client report, command and state requests.
ClientRequestBurst3Burst that request limiter allows.
ClientTimeout10Seconds 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