Skip to content

Setup

Four steps, from nothing to a console your moderators can use. Each one adds a single thing, and each shows what changed.

1. Start it

Make a folder in ServerScriptService and put one Script inside it. Everything you add later goes in this folder too, so it is worth setting up now:

ServerScriptService
└── Admin              ← a Folder
    └── StartAdmin     ← a Script

StartAdmin:

luau
local ServerStorage = game:GetService("ServerStorage")
local Operator = require(ServerStorage:WaitForChild("Operator"))

Operator.Start({
	DefaultCommands = { "debug" },
})

The folder matters because later steps use script.Parent to find things next to the script. If you put StartAdmin directly in ServerScriptService instead, script.Parent is ServerScriptService and step 3 will not find your commands.

Press F2 in game. You have a console. Try help, then ping.

It survives respawning and rejoining; you do not need to reopen anything after dying.

What changed. You can run the four debug commands. Nobody else can: with no roles configured, Operator falls back to the place owner only and logs a notice saying so. Everyone else receives nothing at all and cannot tell it is installed.

2. Let your team in

Add Roles:

luau
Operator.Start({
	DefaultCommands = { "debug" },

	Roles = {
		moderator = { GroupId = 7654321, MinRank = 200 },
	},
})

What changed. Anyone at rank 200 or above in that group now gets the console too.

Finding your group ID. Open the group on the Roblox site; it is the number in the URL. roblox.com/groups/7654321/My-Group means GroupId = 7654321.

Ranks run 1 to 255. 255 is the owner, and the ranks in between are whatever your group defines under Configure Group → Roles, where each role shows its rank number. MinRank = 200 means "this role and anything above it", so pick a number below your moderator role's rank and above everyone else's.

If you would rather list people directly, use UserIds = { 1234567 } instead, with no group needed. A role can have both, and matches if either applies.

Commands with no :permission(...) run for anyone holding at least one role, so all four debug commands are now available to your moderators. Permissions covers inheritance and custom sources.

3. Add a command of your own

Make a folder and put one ModuleScript in it:

ServerScriptService
└── Admin
    ├── StartAdmin       ← the Script from step 1
    └── Commands
        └── announce

announce:

luau
local Operator = require(game:GetService("ServerStorage").Operator)
local Command, Types = Operator.Command, Operator.Types

export type AnnounceArgs = { message: string }

local announce = Command.new("announce") :: Operator.Builder<AnnounceArgs>

return announce
	:description("Show a message to everyone")
	:arg(Types.String, "message", "What to say", { rest = true })
	:run(function(context, args)
		print(args.message)
		context:reply("announced")
	end)

Point Start at the folder:

luau
local admin = script.Parent

Operator.Start({
	Commands = admin.Commands,
	DefaultCommands = { "debug" },

	Roles = {
		moderator = { GroupId = 7654321, MinRank = 200 },
	},
})

What changed. Type announce hello everyone and it runs.

Types.String is an argument type: it turns what someone typed into a real value before your handler sees it. Nine ship, including Types.Player, which resolves a name to an actual Player; see Argument Types. { rest = true } is why the whole remainder of the line becomes one string instead of just the first word.

Index the folder directly

admin.Commands errors immediately if the folder is missing or misspelled. admin:FindFirstChild("Commands") returns nil silently, and Operator then starts with no commands and no explanation of why.

4. Turn on moderation

luau
Operator.Start({
	Commands = admin.Commands,
	DefaultCommands = { "debug", "moderation" },

	Roles = {
		moderator = { GroupId = 7654321, MinRank = 200 },
	},
})

What changed. kick, ban, unban, bring, goto and logs are now available, and everything anyone runs is recorded. Try logs to see it.

ban and unban are marked destructive, so they ask for confirmation before running.

That is a working setup. From here:

Released under the MIT Licence.