> ## Documentation Index
> Fetch the complete documentation index at: https://docs.readmin.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom Remote Admin Commands

> Build your own in-game commands and register them with ReAdmin by passing a folder to the Activity Tracking loader.

## Overview

ReAdmin ships with a set of built-in Remote Admin commands (`ban`, `kick`, `warn`,
`promote`, `demote`, `rank`, `suspend`, `terminate`, and `find`). On top of these,
you can write **your own commands** in Luau and hand them to the ReAdmin loader.

When the game starts, ReAdmin:

1. Loads every command module you provide.
2. Registers each command (its `id`, `name`, and parameters) with the ReAdmin API.
3. Surfaces those commands in your [Remote Admin](/settings/remote-admin)
   settings so you can grant or deny them per role, department, or individual user.
4. Listens for command usage in chat **and** from the dashboard, resolves the
   parameters, checks permissions, and calls your `executor` function.

This means a custom command you write once is permission-gated, audited, and
usable from both in-game chat and the ReAdmin web dashboard — no extra wiring
required.

<Note>
  Custom commands run on the **server**. Treat them like any other server-side
  admin code: only logic you trust should ship inside a command module.
</Note>

## How commands are loaded

The loader accepts an optional `commands` field. It must be a `Folder` whose
children are `ModuleScript`s — one module per command.

```lua theme={null}
local ReAdmin = require(YOUR_LOADER_ASSET_ID)

ReAdmin({
    loaderId = "your-loader-id-here",
    afkTime = 45,

    -- Pass a Folder of ModuleScripts. Each ModuleScript returns one command.
    commands = script.Parent:WaitForChild("MyCommands"),
})
```

Internally ReAdmin calls `commands:GetChildren()` and requires each child. Any
child that is **not** a `ModuleScript`, or that fails to return a valid command
table, is skipped with a warning — one bad command will never stop the rest from
loading.

<Tip>
  You can keep using ReAdmin's built-in commands and add your own at the same time.
  The commands in the module's internal `Commands` folder are always loaded; the
  folder you pass in `commands` is loaded **in addition** to them.
</Tip>

## Anatomy of a command

Every command module returns a single table with four required fields:

| Field        | Type       | Description                                                                                                                                           |
| ------------ | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`         | `string`   | A **globally unique** identifier for the command. The dashboard uses this to track permissions, so it must never collide with another command's `id`. |
| `name`       | `string`   | The word players type in chat (e.g. `name = "ban"` → `/ban`). Should be unique and memorable.                                                         |
| `paramaters` | `array`    | An ordered list of parameter definitions. Use an empty array `{}` if the command takes no arguments.                                                  |
| `executor`   | `function` | The function ReAdmin calls when the command runs and the user is permitted.                                                                           |

<Warning>
  The field is spelled **`paramaters`** (matching the module's internal API). Use
  that exact spelling or your command will fail to load.
</Warning>

### Parameter definitions

Each entry in `paramaters` is a table:

```lua theme={null}
{
    id = "player",            -- key used to read the value in your executor
    paramaterType = "player", -- how ReAdmin parses the user's input
    optional = false,         -- if true, the command runs even when omitted
    fallbackFind = true,      -- (player type only) search Roblox for offline users
}
```

| Option          | Type       | Description                                                                                                                                                      |
| --------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`            | `string`   | The key you read inside the executor via `args.paramaters[id]`.                                                                                                  |
| `paramaterType` | `string`   | One of the parameter types below.                                                                                                                                |
| `optional`      | `boolean?` | When `true`, the command still executes if the value is missing. When `false`/omitted, ReAdmin blocks execution and notifies the user that the value is missing. |
| `fallbackFind`  | `boolean?` | Only used by `player`. See below. Defaults to `true`.                                                                                                            |

#### Parameter types

| `paramaterType` | Resolves to                   | Notes                                                                                                                                                                                                                                                             |
| --------------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `player`        | A `Player` (or pseudo-player) | Matches an in-game player by the start of their username or by exact UserId. If no one in the server matches and `fallbackFind` is enabled, ReAdmin queries Roblox for an **exact** username match and returns a pseudo-player `{ UserId, Name, psuedo = true }`. |
| `text`          | `string`                      | A single word/token.                                                                                                                                                                                                                                              |
| `long_text`     | `string`                      | Text wrapped in quotes, e.g. `"this is a test"`. If it is the **last** parameter, quotes are optional — all remaining words are joined together.                                                                                                                  |
| `number`        | `number`                      | A numeric value (parsed with `tonumber`).                                                                                                                                                                                                                         |

<Note>
  `fallbackFind` lets you action players who are not currently in the server (handy
  for `ban`). The returned pseudo-player only contains `UserId` and `Name`. If you
  need more than that, reach out in our [Discord](https://discord.gg/readmin-845829826626584606).
</Note>

### The executor function

`executor` receives a single `args` table:

```lua theme={null}
type commandArgs = {
    actioner: Player?,                  -- the staff member who ran the command
    paramaters: any?,                   -- resolved parameter values, keyed by id
    actionerReAdminPermissions: any?,   -- the actioner's ReAdmin permissions
    rawMessage: string?,                -- the original chat message
    source: "chat" | "website",         -- where the command was triggered
    command: any,                       -- the command definition itself
    apiWrapper: ReAdminAPI,             -- authenticated ReAdmin API client
}
```

A few important guarantees ReAdmin makes before your executor runs:

* **Permissions are already checked.** The executor is only called if the
  `actioner` has been granted this command's `id` in Remote Admin settings.
* **Required parameters are already validated.** Any non-`optional` parameter is
  guaranteed to be present, so you can dereference `args.paramaters.player.UserId`
  without nil-checking it.

## The `apiWrapper`

The `apiWrapper` is an authenticated ReAdmin API client. It exposes the same
actions ReAdmin's built-in commands use, so your custom commands can create real
records that show up across the dashboard. Common methods include:

| Method                                                                                                    | Purpose                                                                             |
| --------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| `apiWrapper:CreateBan({ userId, reason, bannedByUserId, expiresInDays })`                                 | Create a [game ban](/features/game-bans).                                           |
| `apiWrapper:CreateStaffHistory({ actioningUser, userId, reason, type, approval_required, notify, role })` | Add a [staff history](/features/staff-history) record (warnings, promotions, etc.). |
| `apiWrapper:ChangeUserRank(userId, rankId)`                                                               | Change a member's group rank via the [Ranking API](/settings/ranking-api).          |
| `apiWrapper:ExileUser(userId)`                                                                            | Exile a member from the group.                                                      |
| `apiWrapper:PostShoutMessage(message)`                                                                    | Post a group shout.                                                                 |

Every method returns a response with a `StatusCode` and `Body`. Check the status
before notifying the user.

<Tip>
  To send feedback to the player who ran the command, fire the `ReAdminEvent`
  RemoteEvent with a `notify` payload (this is how the built-in commands report
  success and failure):

  ```lua theme={null}
  game.ReplicatedStorage.ReAdminEvent:FireClient(args.actioner, {
      ["type"] = "notify",
      message = "Done!",
  })
  ```
</Tip>

## Full example

Create a `ModuleScript` (for example `MyCommands/Compliment`) that returns a
command. This command takes a player and an optional message, and logs a
positive staff history note:

```lua theme={null}
type commandArgs = {
    actioner: Player?,
    paramaters: any?,
    actionerReAdminPermissions: any?,
    rawMessage: string?,
    source: "chat" | "website",
    command: any,
    apiWrapper: any,
}

return {
    id = "mygroup_compliment",
    name = "compliment",
    paramaters = {
        {
            id = "player",
            paramaterType = "player",
            fallbackFind = true,
        },
        {
            id = "reason",
            paramaterType = "long_text",
            optional = true,
        },
    },
    executor = function(args: commandArgs)
        local actioner = args.actioner
        local target = args.paramaters.player
        local reason = args.paramaters.reason or "Outstanding work!"

        local result = args.apiWrapper:CreateStaffHistory({
            actioningUser = actioner.UserId,
            userId = target.UserId,
            reason = reason,
            ["type"] = "note",
            notify = true,
        })

        local ok = result.StatusCode == 200 or result.StatusCode == 201
        game.ReplicatedStorage.ReAdminEvent:FireClient(actioner, {
            ["type"] = "notify",
            message = ok
                and ("Complimented " .. target.Name)
                or ("Could not compliment " .. target.Name),
        })
    end,
}
```

Then register the folder with the loader:

```lua theme={null}
ReAdmin({
    loaderId = "your-loader-id-here",
    commands = script.Parent:WaitForChild("MyCommands"),
})
```

A staff member can now type `/compliment builderman Great session today!` in
chat — but only if they have been granted the `mygroup_compliment` permission in
[Remote Admin](/settings/remote-admin) settings.

## Best practices

* **Use a unique, namespaced `id`** (e.g. `mygroup_<action>`) to avoid clashing
  with ReAdmin's built-ins or another command.
* **Validate the result** of every `apiWrapper` call before telling the user it
  succeeded.
* **Keep executors fast and defensive.** ReAdmin wraps command parsing in a
  `pcall`, but a hung executor still ties up the request.
* **Grant before you test.** A newly added command is denied to everyone by
  default — enable it for yourself in Remote Admin settings first.

## Related pages

* [Remote Admin settings](/settings/remote-admin)
* [Calls](/features/calls)
* [Downloads & installation](/settings/downloads)
* [Ranking Module](/api-reference/ranking-module)
