Skip to content

Creating Commands

Your first command

HD Admin commands are plain ModuleScripts. To create your own, add a ModuleScript under HD Admin > Config > Commands (folders are fine if you want to group things) and return an array of command tables. One module can hold as many commands as you like:

--!strict
local modules = script:FindFirstAncestor("HD Admin").Core.MainModule.Value.Modules
local Task = require(modules.Objects.Task)

local commands: Task.Commands = {

    {
        name = "Shield",
        aliases = {"Bubble"},
        tags = {"PackMod"}, -- (1)!
        args = {"Player"},
        run = function(task: Task.Class, args: {any})
            local target = unpack(args)
            local character = target.Character
            if not character then
                return
            end
            task:keep("UntilTargetRespawns") -- (2)!
            local field = task.janitor:add(Instance.new("ForceField")) -- (3)!
            field.Parent = character
        end
    },

}
return commands
  1. Tags bind the command to roles. The default Mod role inherits everything tagged PackMod, so your Mods can use this straight away.
  2. Keeps the task alive after run returns, until the target respawns.
  3. Anything added to the janitor is destroyed when the task ends, which is what makes ;unshield work without any teardown code.

That's a complete command. name is what players type after the prefix, so ;shield me wraps you in a forcefield and ;unshield me removes it. aliases are alternative names that behave identically. args lists the arguments the command accepts by name; each one is resolved for you and passed into run in order. run is then called with a task, the object that manages the command's lifetime.

Roles gain commands through their inheritCommandsWithTags array, and senior roles pick tagged commands up too through inheritCommandsFromJuniorRoles. See Roles for more.


Command fields

Every field except name is optional. These are the ones you'll reach for most:

1. name {required}

What players type after the prefix to run the command. It doubles as the undo name, so Shield is ended with ;unshield.

2. aliases {optional}

Alternative names that run the same command, like {"Bubble"}.

3. undoAliases {optional}

Names that undo the command instead of running it. The built-in NoClip has undoAliases = {"Clip"}.

4. tags {optional}

Binds the command to roles. A role can use any command whose tags overlap its inheritCommandsWithTags array. The built-in packs use tags like "PackMod" and "PackAdmin".

5. order {optional}

Sorts the Commands page. Lower numbers appear first.

6. groups {optional}

Commands in the same group undo each other when another one runs. Fly and NoClip share the "Flight" group, so starting one ends the other.

7. args {optional}

An array of argument names, resolved in order and passed into run. Use built-in names like "Player", "Number" and "SoundId", or your own custom arguments (see below).

8. cooldown {optional}

Seconds that must pass after the command finishes before it can be run again.

9. autoPreview {optional}

Opens the command in the Run page instead of executing straight away. The built-in ban command uses this so details are confirmed first.

10. hide {optional}

Hides the command from the Commands page.

11. disableModifiers {optional}

Silently strips any Modifiers from the call and hides the Modifiers picker on the Run page.

12. disableGlobalModifiers {optional}

Rejects the global, perm and join modifiers for this command. Useful when a command should never reach beyond the current server.


The task object

run receives a task, created fresh for every target. It handles cleanup, persistence and live updates so you don't have to.

task:keep(persistence) keeps the task alive after run returns (tasks are otherwise destroyed immediately). Your options are "UntilTargetRespawns", "UntilTargetLeaves", "UntilCallerRespawns", "UntilCallerLeaves" and "Indefinitely".

task.janitor:add(item) registers instances, connections or functions for cleanup. Everything added is destroyed when the task ends, which is what makes ;unshield and command groups work without you writing any teardown code.

task:getArg(name, default) and task:setArg(name, value) read and write arguments by name. When the player didn't provide the argument, getArg falls back to default and remembers it.

task.client:run(player, ...) invokes your matching client command on that player's device (next section).

task:redo(player, callback) calls callback immediately, then again every time the player respawns while the task is alive. Handy for effects that would otherwise vanish on death.


Client commands

Some commands need code on the player's device, like flight, which reads input every frame. Add a child ModuleScript named Client to your command module and return entries whose names match your server commands:

local modules = script:FindFirstAncestor("HD Admin").Core.MainModule.Value.Modules
local Task = require(modules.Objects.Task)

local clientCommands: Task.ClientCommands = {

    {
        name = "Fly",
        run = function(task: Task.Class, speed: number)
            -- This runs on the target's device
        end,
    },

}
return clientCommands

The server half then hands over whenever it likes:

run = function(task: Task.Class, args: {any})
    local target = unpack(args)
    task:keep("UntilTargetRespawns")
    task.client:run(target, task:getArg("FlightSpeed", 50))
end

Info

You don't replicate anything yourself. The framework detects any child module named Client and ships it to players automatically.


Custom arguments

Built-in argument names cover most cases. When you need your own, register them inside HD Admin > Config > Args:

return function(Args)
    return {

        ["Scale"] = Args.createAliasOf("Number", {
            minValue = 0.1,
            maxValue = 4,
            stepAmount = 0.1,
            defaultValue = 2,
            logarithmic = true,
        }),

    }
end

This creates a Scale argument based on Number with its own minimum, maximum, step and default, ready to use in any command's args. Everything you register here also appears in argument autocompletes, and displayName lets you relabel an argument in menus.

Warning

Make sure the name you choose doesn't match any of HD Admin's built-in arguments.


Selling commands

Custom commands are yours to sell. Tag them with something unique, then on the role you'd like to sell, add your gamepass or asset to giveToProducts and the tag to inheritCommandsWithTags:

giveToProducts = {
    {type = "GamePass", id = 123456},
},
inheritCommandsWithTags = {"VIP"},

Anyone who owns the product receives the role automatically, along with every command tagged "VIP". This is how creators earn Robux from their commands. See Roles for the full breakdown of role configuration.