Skip to main content
The CommandRegistry API provides a declarative way to create custom commands with automatic argument parsing, subcommands, and tab completion.

Overview

Essential’s Command API automatically describes command arguments through handler function parameters, eliminating the need for manual argument parsing. Commands support:
  • Default handlers for commands with no arguments
  • Subcommands (e.g., /command subcommand)
  • Automatic type conversion for parameters
  • Optional parameters using nullable types
  • Custom argument parsers for complex types

Getting the CommandRegistry

val registry = EssentialAPI.getCommandRegistry()
CommandRegistry registry = EssentialAPI.getCommandRegistry();

Creating a command

Create a class or object that extends Command:
object MyCommand : Command(
    name = "mycommand",
    autoHelpSubcommand = true,
    hideFromAutocomplete = false
) {
    @DefaultHandler
    fun handle() {
        // Called when user types /mycommand with no arguments
    }
}
name
String
required
The command name (e.g., “mycommand” for /mycommand)
autoHelpSubcommand
Boolean
default:"true"
Whether to automatically generate a help subcommand showing all available subcommands and their usages
hideFromAutocomplete
Boolean
default:"false"
Whether to hide this command from Minecraft’s tab completion

Registering commands

Register your command with the command system:
MyCommand.register()
Or using the registry directly:
EssentialAPI.getCommandRegistry().registerCommand(MyCommand)

Command handlers

Default handler

Handles invocations with no subcommands:
@DefaultHandler
fun handle() {
    // Your logic here
}

Subcommands

Create subcommands using the @SubCommand annotation:
@SubCommand("reload", aliases = ["r"], description = "Reload configuration")
fun handleReload() {
    // Called when user types /mycommand reload
}
value
String
required
The subcommand name
aliases
Array<String>
default:"[]"
Alternative names for this subcommand
description
String
default:""
Description shown in help text

Command arguments

Define arguments by adding parameters to your handler functions. Essential automatically parses and validates them:

Basic types

@DefaultHandler
fun handle(number: Int, message: String) {
    // Usage: /mycommand <number> <message>
}
Supported types: String, Int, Double, Boolean

Optional arguments

Use nullable types in Kotlin or Optional in Java:
@DefaultHandler
fun handle(number: Int, optional: Boolean?) {
    // /mycommand 5 -> number=5, optional=null
    // /mycommand 5 true -> number=5, optional=true
}
void handle(int number, Optional<Boolean> optional) {
    // Same behavior in Java
}

Parameter annotations

@DisplayName

Provide custom parameter names for usage messages:
@DefaultHandler
fun handle(@DisplayName("playerName") name: String) {
    // Usage: /mycommand <playerName>
}

@Greedy

Consume all remaining arguments:
@DefaultHandler
fun handle(@Greedy message: String) {
    // /mycommand hello world -> message="hello world"
}
@Greedy should be used on the last parameter, as it consumes all remaining input.

@Quotable

Allow quoted strings to contain spaces:
@DefaultHandler
fun handle(@Quotable message: String, name: String) {
    // /mycommand "hello world" player -> message="hello world", name="player"
    // /mycommand hello player -> message="hello", name="player"
}

@Options

Restrict parameter to specific values:
@SubCommand("mode")
fun handleMode(@Options(["easy", "normal", "hard"]) difficulty: String) {
    // Only allows: /mycommand mode easy|normal|hard
}

@Take

Consume a specific number of arguments:
@DefaultHandler
fun handle(@Take(3, allowLess = false) coords: String) {
    // Requires exactly 3 arguments
}
value
Int
required
Number of arguments to consume
allowLess
Boolean
default:"true"
Whether to allow fewer arguments than specified

Custom argument parsers

For custom types, implement ArgumentParser and register it:
class PlayerParser : ArgumentParser<Player> {
    override fun parse(arguments: ArgumentQueue, param: Parameter): Player? {
        val name = arguments.poll()
        return Minecraft.getMinecraft().theWorld?.playerEntities
            ?.find { it.name.equals(name, ignoreCase = true) }
    }
    
    override fun complete(arguments: ArgumentQueue, param: Parameter): List<String> {
        return Minecraft.getMinecraft().theWorld?.playerEntities
            ?.map { it.name } ?: emptyList()
    }
}

// Register the parser
registry.registerParser(Player::class.java, PlayerParser())

ArgumentQueue methods

poll()
String
Get the next argument and remove it from the queue. Throws exception if queue is empty.
peek()
String?
Get the next argument without removing it. Returns null if queue is empty.
isEmpty()
Boolean
Check if there are no more arguments available.
Implement the complete() method in your parser to provide tab completion suggestions.

Command aliases

Define global aliases for your command:
object MyCommand : Command("mycommand") {
    override val commandAliases = setOf(
        Alias("mc"),
        Alias("mycmd", hideFromAutocomplete = true)
    )
}

Unregistering commands

Remove a command from the registry:
EssentialAPI.getCommandRegistry().unregisterCommand(MyCommand)

Complete example

object TeleportCommand : Command("tp", autoHelpSubcommand = true) {
    
    override val commandAliases = setOf(Alias("teleport"))
    
    @DefaultHandler
    fun handleDefault() {
        EssentialAPI.getNotifications().push(
            "Teleport",
            "Use /tp <player> or /tp <x> <y> <z>"
        )
    }
    
    @SubCommand("player", description = "Teleport to a player")
    fun handlePlayer(target: String) {
        val player = findPlayer(target)
        if (player != null) {
            // Teleport logic
            EssentialAPI.getNotifications().push(
                "Teleport",
                "Teleported to ${player.name}"
            )
        }
    }
    
    @SubCommand("coords", description = "Teleport to coordinates")
    fun handleCoords(
        @DisplayName("x") xPos: Double,
        @DisplayName("y") yPos: Double,
        @DisplayName("z") zPos: Double
    ) {
        // Teleport to coordinates
        EssentialAPI.getNotifications().push(
            "Teleport",
            "Teleported to $xPos, $yPos, $zPos"
        )
    }
    
    private fun findPlayer(name: String) = 
        Minecraft.getMinecraft().theWorld?.playerEntities
            ?.find { it.name.equals(name, ignoreCase = true) }
}

// Register the command
TeleportCommand.register()

Best practices

Use object instead of class in Kotlin for commands to create a singleton instance.
Parameter names from your source code are used in usage messages. Use @DisplayName if your build process strips parameter names or if you want custom names.
Enable autoHelpSubcommand to automatically generate a help command that lists all available subcommands.

Build docs developers (and LLMs) love