Registering commands
Declare and handle commands from a Python plugin, plus the usage-string syntax.
This page covers how to declare and handle commands in Python, from a one-line command up to sub-commands and sender checks, plus the usage-string syntax that drives parameters, types, and tab-completion.
Declare a command
Commands are declared as a commands class attribute on your plugin - a dict keyed by command name:
from endstone.plugin import Plugin
class MyPlugin(Plugin):
api_version = "0.11"
commands = {
"hello": {
"description": "Greet the command sender.",
"usages": ["/hello"],
}
}Each entry takes a description and a list of usages - the usage strings the client parses for tab-completion.
api_version is the major.minor of the Endstone API you build against. Set it to match your installed Endstone.
Control who can run it
By default every command requires the operator permission. Attach a permission to open a command up or lock it down, and set its default - see Permissions.
Handle the command
Override on_command to run when the command is executed. Return True if you handled it, or False to show the usage message:
from endstone.command import Command, CommandSender
from endstone.plugin import Plugin
class MyPlugin(Plugin):
# ...
def on_command(self, sender: CommandSender, command: Command, args: list[str]) -> bool:
if command.name == "hello":
sender.send_message("Hello World!")
return TrueRead parameters
args holds the parsed parameters in order. Declaring /hello [msg: message] makes msg optional, so check whether it was provided:
commands = {
"hello": {
"description": "Greet the command sender.",
"usages": ["/hello [msg: message]"],
"permissions": ["my_plugin.command.hello"],
}
}
def on_command(self, sender: CommandSender, command: Command, args: list[str]) -> bool:
if command.name == "hello":
if len(args) == 0:
sender.send_message("Hello World!")
else:
sender.send_message(args[0])
return TrueBranch on a sub-command
A single command can offer a fixed set of actions with an enum parameter. Declare the choices in the usage string and dispatch on the first argument:
commands = {
"warp": {
"description": "Manage warps.",
"usages": ["/warp <add|list|del>"],
}
}
def on_command(self, sender: CommandSender, command: Command, args: list[str]) -> bool:
if command.name == "warp":
action = args[0]
if action == "add":
sender.send_message("Warp added.")
elif action == "list":
sender.send_message("Listing warps...")
elif action == "del":
sender.send_message("Warp deleted.")
return TrueBecause the parameter is mandatory, the client only accepts one of the listed values, so args[0] is always one of them.
Restrict to players
A command can be run from the console as well as in-game. When your logic needs an actual player, guard with isinstance and bail out gracefully otherwise:
from endstone import Player
from endstone.command import Command, CommandSender
def on_command(self, sender: CommandSender, command: Command, args: list[str]) -> bool:
if command.name == "ping":
if not isinstance(sender, Player):
sender.send_message("Only players can run this command.")
return True
sender.send_popup("Pong!")
return TrueUsage strings
Endstone commands are declared as usage strings: you write the command the way a player types it, and Endstone parses that string to build the argument structure and wire up Bedrock's native, client-side tab-completion for you. What you write is what the player sees.
Most server frameworks make you trade one thing for another:
- Brigadier-style builders (Bukkit/Paper) have you assemble a tree of argument nodes by hand, nesting builder calls inside builder calls. It's powerful, but verbose, and the shape of the command is buried in the code.
- Nukkit-style registration keeps the code flat, but you lose Bedrock's client-side autocomplete - players get no parameter hints as they type.
Endstone takes neither path. You declare a single line - /give <player: target> <item: str> [amount: int] - and Endstone parses it into typed parameters, registers them so the Bedrock client shows native tab-completion and parameter hints, and hands your handler the parsed arguments.
Parameters
A usage string is the command name followed by zero or more parameters. There are two kinds:
| Kind | Description | Syntax |
|---|---|---|
| Mandatory | Must be provided | <name: type> |
| Optional | Can be omitted | [name: type] |
So /hello [msg: message] declares one optional msg parameter of type message; switch the brackets to /hello <msg: message> to make it mandatory. Parameters read left to right, exactly as the player types them.
Overloads
A command can declare multiple usage strings, and each one is an overload. The client suggests whichever overloads match what the player has typed so far - the same way vanilla commands like /give offer different forms. Endstone merges them into a single autocomplete tree.
Built-in types
Each parameter has a type that drives both validation and the client's suggestions. Endstone ships these out of the box:
| Type | Alias | Description | Example |
|---|---|---|---|
int | An integer | 10 | |
float | A floating-point number | 3.14 | |
bool | A boolean | true | |
target | actor, entity, player | A target selector | @e, @r, PlayerName |
str | string | A string, terminated by a space | Hello |
block_pos | vec3i | A 3D integer position | 1 2 3 |
pos | vec3, vec3f | A 3D float position | 1.0 2.0 3.0 |
message | Everything until the end of the line | Hello World! | |
json | A JSON string | {"key": "value"} | |
block | A block type | wood | |
block_states | Block states | ["wood_type"="birch","stripped_bit"=true] | |
entity_type | An entity type | minecraft:creeper |
Because the message type captures everything up to the end of the line, it must be the last parameter in a usage string - Endstone refuses to register a command that places any parameter after a message.
Enum parameters
To accept one of a fixed set of values, use an enum parameter. There are two ways to write one.
Short form
List the values right inside the parameter brackets, separated by |, with no name or type:
- Mandatory:
<value1|value2|value3> - Optional:
[value1|value2|value3]
For example, /home <add|list|del> lets the player pick add, list, or del, and the client suggests exactly those. Endstone generates the parameter name and the enum type for you, so this is the quickest way to a choice parameter - prefer it for up to about five values.
Full form
The Bedrock client only spells out the inline add|list|del hint while the set is small. Past about five values it stops listing them and shows the parameter as <name: EnumType> instead - and with the short form that name is the one Endstone auto-generated by concatenating the command name and every value (/home <add|list|del> becomes HomeAddListDel), which reads as noise to the player. Past about five values, name the type yourself so the hint stays meaningful: put the values in parentheses, then give the parameter a name and an enum type:
- Mandatory:
(value1|value2|value3)<name: EnumType> - Optional:
(value1|value2|value3)[name: EnumType]
For example, /warp (home|spawn|shop|mine|nether|end)<destination: WarpTarget> declares a destination parameter of type WarpTarget. A named type stays readable and can be reused across commands.
Enum types behave like string-literal unions in TypeScript or Python: a named set of allowed constants. Because the client autocompletes them, players never have to guess the valid values.