Skip to Content
1robie

Sub-commands

Nesting commands, how aliases and requirements behave down the tree, and where sub-commands collide with arguments.

/guild create <name> rather than a separate /guildcreate. A sub-command is a literal token that branches the command tree.

The type is the same one

BaseCommand<T> extends SubCommand<T>. Everything on this page — arguments, flags, requirements, nesting — works identically on both. The only things BaseCommand adds are the two properties that exist because it is what the server registers:

Only on BaseCommandWhy
setDescription(String)Shown in /help; a nested node has no help entry
setReloadable(boolean)unregisterReloadableCommands() operates on roots

So a sub-command is declared exactly like a command, minus those two:

public class GuildCreateSubCommand extends SubCommand<MyPlugin> {

    public GuildCreateSubCommand(MyPlugin plugin) {
        super(plugin, "create", "new");        // name + aliases, as usual
        this.setPermission("myplugin.guild.create");
        this.addRequiredArgument("name", StringArgumentType.word());
    }

    @Override
    protected CommandResultType perform(CommandDispatch<MyPlugin> dispatch) {
        String name = dispatch.getArgument("name", String.class);
        ...
        return CommandResultType.SUCCESS;
    }
}

Attach it to the parent:

public class GuildCommand extends BaseCommand<MyPlugin> {

    public GuildCommand(MyPlugin plugin) {
        super(plugin, "guild", "g");
        this.setDescription("Guild management");

        this.addSubCommand(new GuildCreateSubCommand(plugin));   // /guild create <name>
        this.addSubCommand(new GuildInviteSubCommand(plugin));   // /guild invite <player>
    }

    @Override
    protected CommandResultType perform(CommandDispatch<MyPlugin> dispatch) {
        // Runs for bare `/guild`. The parent stays executable on its own.
        dispatch.getSender().sendMessage("/guild create | invite");
        return CommandResultType.SUCCESS;
    }
}

Only the root is tracked with the manager. Sub-commands come along with it.

The parent stays executable

Adding sub-commands does not consume the parent. perform on the root still runs for /guild with nothing after it, which is the natural place for a usage message.

Nesting is unlimited

addSubCommand takes a SubCommand<T>, and SubCommand has addSubCommand, so /guild admin purge <name> is three levels of the same construction. Nothing in the library caps the depth.

Without subclassing

SubCommand.builder(...) mirrors BaseCommand.builder(...):

SubCommand<MyPlugin> info = SubCommand.builder(this, "info")
        .alias("i")
        .permission("myplugin.guild.info")
        .executes(dispatch -> {
            dispatch.getSender().sendMessage("Guild info");
            return CommandResultType.SUCCESS;
        })
        .build();

Mixing styles is fine — a subclassed parent can take a built sub-command and the other way round.

Aliases: one level only

A sub-command's aliases are built only under the command's real name, not under the parent's aliases.

Given /guild with alias /g, and a create sub-command with alias new:

InputWorks
/guild createyes
/guild newyes
/g createyes
/g newno

The alias tree is deliberately not a full cross-product — every combination is real Brigadier nodes, sent to every client. If /g new matters, give the sub-command a name people will type and skip the alias.

Requirements apply to the whole branch

Each node carries its own requirement predicate, and Brigadier checks every node along a path. The effect is that a parent's requirement gates everything beneath it, and a child's requirement stacks on top:

// on GuildCommand
this.setPermission("myplugin.guild");
// on GuildCreateSubCommand
this.setPermission("myplugin.guild.create");

/guild create now needs both nodes. There is no inheritance to configure and no way to loosen a parent's requirement from a child — see Requirements & permissions.

Sub-commands and arguments compete

A literal and an argument in the same position are ambiguous, and Brigadier resolves literals first:

this.addSubCommand(new GuildListSubCommand(plugin));      // literal `list`
this.addOptionalArgument("name", StringArgumentType.word());

/guild list runs the sub-command; the argument never sees the value list. Everything else still reaches the argument. This is only a problem when a legal argument value happens to equal a sub-command name — the same class of trap as a value that looks like a flag.

Flags come after the whole path

Flags declared on a node attach to that node's executable positions, and flag nodes branch only into other flag nodes:

/guild create MyGuild --silent     parses
/guild --silent create MyGuild     does not

Flags are attached to the executable positions of the node that declares them, so they do not leak down the tree: a flag on guild is not available on guild create. Declare each flag on the node that reads it, and keep the count down — the flag tree cost is paid once per executable position of that node, and every optional argument adds another such position.

Next

Last updated on

On this page