Paper Plugin Guide
File mode0

Paper essentials

Commands, part 2: Brigadier command trees

Build commands with typed arguments, tab completion and subcommands that the client understands.

Intermediate49 min read

Brigadier is the command system Minecraft itself uses. On this page you will describe commands as trees, let the game check numbers, player names and items for you, add subcommands and custom suggestions, and build /heal, /gm and /tools.

Why Brigadier?

In part 1 you wrote /roll [sides] with a BasicCommand. It worked, but you did all the checking yourself: is there an argument, is it a number, is it between 2 and 100? And while the player typed, the game had no idea what /roll expected.

Brigadier turns that around. Instead of receiving loose words, you describe the shape of the command: "first the word roll, then optionally a whole number from 2 to 100". Paper sends that shape to the player's game, and three good things happen:

  • The game checks the input while the player types. A wrong argument turns red before they press Enter, with an error that says what is wrong.
  • You get real values, not text. Your code receives an int, a Player, an ItemStack or a GameMode, already checked.
  • Vanilla features come for free. Selectors such as @a and @p, item syntax like diamond_sword[enchantments={sharpness:5}], and argument hints in the chat box all work in your commands, because they are the same argument types vanilla commands use.

The price is a new way of writing commands. It looks strange at first, so this page goes slowly.

Commands are trees

Think of vanilla's /gamemode, and of the /gm shortcut you will build later on this page. After /gm you must type a mode, and after the mode you may type a player name. Brigadier stores this as a command tree. Every node in the tree is one word of the command, and there are two kinds:

  • A literal is a fixed word that the player types exactly, such as gamemode, or give in /tools give.
  • An argument is a value the player chooses, such as a number, a player or a game mode. It has a name (so your code can ask for it) and a type (so the game can check it).
gm no executes: incomplete <mode> runs gameMode(): creative, survival... <player> runs player(): one player Valid ways to type it /gm creative /gm creative Alex (stops at a "runs" node) literal argument has executes
A small command tree, the one you will build for /gm. The root is the command's name. A command may end only at a node marked runs, so each path from the root to a runs node is one valid way to type it.

Each node can also have an executes part: the code that runs when the command ends at that node. If the player stops at a node without executes, the command is incomplete and the game says so. This page calls the nodes with executes stations, because those are the places where a command may stop.

Your first tree: /roll again

The smallest possible Brigadier command is one literal with an executes. Read it as "a command called hello that, when it ends here, sends a message". You will see where code like this goes in a moment:

The smallest Brigadier command
1Commands.literal("hello")2    .executes(context -> {3        context.getSource().getSender().sendRichMessage("<green>Hello!");4        return Command.SINGLE_SUCCESS;5    })6    .build();

Now the real thing: /roll from part 1, rebuilt as a tree. The tree has two stations. /roll alone rolls a six-sided die, and /roll <sides> rolls a die with that many sides:

RollCommand.javaCompiles on Paper 26.3Compile Lab
Where this file livescommands-brigadier-firstsrcmainjavacomexamplecommandsbrigadierfirstRollCommand.java

The package com.example.commandsbrigadierfirst is the folder path com/example/commandsbrigadierfirst inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • commands-brigadier-first/
    • src/main/
      • java/com/example/commandsbrigadierfirst/Package com.example.commandsbrigadierfirst
        • BrigadierFirstPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • RollCommand.javayou are hereCommand (Brigadier tree)
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

1package com.example.commandsbrigadierfirst;2 3import com.mojang.brigadier.Command;4import com.mojang.brigadier.arguments.IntegerArgumentType;5import com.mojang.brigadier.context.CommandContext;6import com.mojang.brigadier.tree.LiteralCommandNode;7import io.papermc.paper.command.brigadier.CommandSourceStack;8import io.papermc.paper.command.brigadier.Commands;9import java.util.concurrent.ThreadLocalRandom;10import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;11 12final class RollCommand {13 14    private RollCommand() {15    }16 17    static LiteralCommandNode<CommandSourceStack> create() {18        return Commands.literal("roll")19            .executes(context -> roll(context, 6))20            .then(Commands.argument("sides", IntegerArgumentType.integer(2, 100))21                .executes(context -> roll(context, IntegerArgumentType.getInteger(context, "sides"))))22            .build();23    }24 25    private static int roll(CommandContext<CommandSourceStack> context, int sides) {26        int result = ThreadLocalRandom.current().nextInt(1, sides + 1);27        context.getSource().getSender().sendRichMessage(28            "<gray>You rolled a <gold><result></gold> on a <sides>-sided die.",29            Placeholder.unparsed("result", String.valueOf(result)),30            Placeholder.unparsed("sides", String.valueOf(sides)));31        return Command.SINGLE_SUCCESS;32    }33}
  1. Brigadier was made by Mojang, so its own classes live in com.mojang.brigadier. Paper's additions live in io.papermc.paper.command.brigadier. You use both.
  2. This class only builds the tree; it is never turned into an object. That is why it has a private constructor and a static method.
  3. Builds and returns the finished tree. static means the method belongs to the class itself, so other code calls RollCommand.create() without making an object first. The return type reads as "a literal node for commands whose source is a CommandSourceStack". The part in angle brackets is a generic type parameter; you can copy it as it is.
  4. The root of the tree: the literal word roll. This is the command's name.
  5. The first station. If the player types just /roll, this lambda runs and rolls a six-sided die. context holds everything about this run of the command.
  6. Adds a child node. Read then as "and after that, the player may type...".
  7. The argument's type: a whole number from 2 to 100. The game rejects anything else before your code ever runs.
  8. Reads the argument called sides as an int. The name must match the name in Commands.argument("sides", ...) exactly.
  9. Finishes the tree. Until you call build() you only have a builder, a plan for the tree.
  10. Both stations share this method, so the rolling code exists only once. It returns an int, because every executes must return a number.
  11. The same source and sender as in part 1: getSource() gives the CommandSourceStack, and that gives the sender.
  12. Means "it worked, once". Command.SINGLE_SUCCESS is simply the number 1.

Compare this with part 1's /roll: no args.length, no Integer.parseInt, no try/catch, no range check. The argument type does all of it. Registering works like part 1's lifecycle event, except that you pass the built tree instead of a BasicCommand:

BrigadierFirstPlugin.javaCompiles on Paper 26.3Compile Lab
Where this file livescommands-brigadier-firstsrcmainjavacomexamplecommandsbrigadierfirstBrigadierFirstPlugin.java

The package com.example.commandsbrigadierfirst is the folder path com/example/commandsbrigadierfirst inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • commands-brigadier-first/
    • src/main/
      • java/com/example/commandsbrigadierfirst/Package com.example.commandsbrigadierfirst
        • BrigadierFirstPlugin.javayou are hereMain class: Paper starts here (named as main in plugin.yml)
        • RollCommand.javaCommand (Brigadier tree)
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

1package com.example.commandsbrigadierfirst;2 3import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;4import java.util.List;5import org.bukkit.plugin.java.JavaPlugin;6 7public final class BrigadierFirstPlugin extends JavaPlugin {8 9    @Override10    public void onEnable() {11        getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event ->12            event.registrar().register(RollCommand.create(), "Roll a die with 2 to 100 sides", List.of("dice")));13    }14}
  1. The same lifecycle event as in part 1. Brigadier commands are always registered here (there is no registerCommand shortcut for trees).
  2. Builds the tree and hands it to the registrar, with a description for /help and the alias /dice.

Here is what players see. A valid roll, and then what happens when someone types /roll 500: the game shows the error and your code never runs.

You rolled a 4 on a 6-sided die.
You rolled a 73 on a 100-sided die.
Integer must not be more than 100, found 500
roll 500<--[HERE]

While typing, the player also sees a gray hint <sides> above the chat box, so they know what comes next. You got all of that by choosing a name and a type.

Reading the chain of dots

Brigadier code is one long expression: Commands.literal(...) followed by .then(...), .executes(...), .requires(...) and finally .build(). This style is called a builder: each method changes the plan and hands the same builder back, so you can keep adding dots.

The tricky part is the brackets. A .then(...) or .executes(...) belongs to whatever node it is written on. Indentation shows this for humans, but Java only looks at the brackets. Compare:

executes on the sides argument (right)
1Commands.literal("roll")2    .then(Commands.argument("sides", IntegerArgumentType.integer(2, 100))3        .executes(context -> roll(context, IntegerArgumentType.getInteger(context, "sides"))))4    .build();
executes on the roll literal (wrong)Has a mistake
1Commands.literal("roll")2    .then(Commands.argument("sides", IntegerArgumentType.integer(2, 100)))3    .executes(context -> roll(context, IntegerArgumentType.getInteger(context, "sides")))4    .build();

In the wrong version, one bracket closed the argument too early. Now the executes belongs to roll, so plain /roll tries to read an argument that was never typed and fails, while /roll 20 has no executes at all and is "incomplete". If a command behaves strangely, count the brackets. IntelliJ highlights the matching bracket when you put the cursor next to one.

What executes returns

Every executes returns an int. Players never see it, but vanilla uses it: /execute store result can save it into a scoreboard, and a command block passes it to a comparator. The convention is:

  • Command.SINGLE_SUCCESS (which is 1) when the command did its job.
  • A count when it did something several times, like "healed 3 players": return 3.
  • 0 when it did nothing.

If in doubt, return Command.SINGLE_SUCCESS.

Argument types

The argument type is where Brigadier saves you work. Pick the type that matches what you need, and the game does the checking. These are the ones you will use most:

TypePlayer typesRead it with
IntegerArgumentType.integer(1, 64)16IntegerArgumentType.getInteger(context, "amount") gives an int
DoubleArgumentType.doubleArg(0.1, 5.0)2.5DoubleArgumentType.getDouble(context, "power") gives a double
BoolArgumentType.bool()true or falseBoolArgumentType.getBool(context, "silent")
StringArgumentType.word()one word: letters, digits and _ - . +StringArgumentType.getString(context, "name")
StringArgumentType.string()one word, or "several words in quotes"StringArgumentType.getString(context, "text")
StringArgumentType.greedyString()everything up to the end of the lineStringArgumentType.getString(context, "message")
ArgumentTypes.player()Steve or @p (exactly one player)context.getArgument("player", PlayerSelectorArgumentResolver.class), then .resolve(...)
ArgumentTypes.players()Steve, @a, @a[distance=..10]same as above; gives a List<Player>
ArgumentTypes.entities()@e[type=zombie], a player nameEntitySelectorArgumentResolver, then .resolve(...)
ArgumentTypes.gameMode()creative, survival...context.getArgument("mode", GameMode.class)
ArgumentTypes.world()minecraft:the_nethercontext.getArgument("world", World.class)
ArgumentTypes.itemStack()diamond, diamond_sword[enchantments={sharpness:5}]context.getArgument("item", ItemStack.class)
ArgumentTypes.blockPosition()10 64 -20 or ~ ~1 ~BlockPositionResolver, then .resolve(...)
ArgumentTypes.namedColor()red, dark_aquacontext.getArgument("color", NamedTextColor.class)

There are three ways to read an argument, and the table shows which one each type uses:

  1. Brigadier's own types (numbers, booleans, strings) have a static helper on their class: IntegerArgumentType.getInteger(context, "amount").
  2. Paper's simple types come out with context.getArgument(name, Type.class), for example GameMode.class or ItemStack.class.
  3. Paper's selector and position types give you a resolver first. A selector like @p means "the nearest player", but nearest to whom? The resolver works that out when you call .resolve(context.getSource()), using the source's location.

Example: /heal [targets]

Time for the demo plugin. /heal heals you; /heal <targets> heals other players, and because the argument is players(), admins can write /heal @a to heal everyone:

HealCommand.javaCompiles on Paper 26.3Compile Lab
Where this file livescommands-brigadiersrcmainjavacomexamplecommandsbrigadierHealCommand.java

The package com.example.commandsbrigadier is the folder path com/example/commandsbrigadier inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • commands-brigadier/
    • src/main/
      • java/com/example/commandsbrigadier/Package com.example.commandsbrigadier
        • CommandsBrigadierPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • GameModeCommand.javaCommand (Brigadier tree)
        • HealCommand.javayou are hereCommand (Brigadier tree)
        • ToolsCommand.javaCommand (Brigadier tree)
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

22static LiteralCommandNode<CommandSourceStack> create() {23    return Commands.literal("heal")24        .requires(source -> source.getSender().hasPermission("commandsbrigadier.heal"))25        .executes(HealCommand::healSelf)26        .then(Commands.argument("targets", ArgumentTypes.players())27            .requires(source -> source.getSender().hasPermission("commandsbrigadier.heal.others"))28            .executes(HealCommand::healTargets))29        .build();30}
  1. Only senders with this permission get the command at all. Others do not see it in tab completion and cannot run it. More on requires below.
  2. A method reference: a short way to say "run the method healSelf of this class". It is the same as context -> healSelf(context).
  3. One or more players: a name, or a selector such as @a or @a[distance=..10].
  4. A second, stronger permission for healing other people. A node can have its own requires, so different branches can need different permissions.

And the methods the two stations run:

HealCommand.javaCompiles on Paper 26.3Compile Lab
Where this file livescommands-brigadiersrcmainjavacomexamplecommandsbrigadierHealCommand.java

The package com.example.commandsbrigadier is the folder path com/example/commandsbrigadier inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • commands-brigadier/
    • src/main/
      • java/com/example/commandsbrigadier/Package com.example.commandsbrigadier
        • CommandsBrigadierPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • GameModeCommand.javaCommand (Brigadier tree)
        • HealCommand.javayou are hereCommand (Brigadier tree)
        • ToolsCommand.javaCommand (Brigadier tree)
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

32private static int healSelf(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {33    Player player = context.getSource().getPlayerOrThrow();34    heal(player);35    player.sendRichMessage("<green>You have been healed.");36    return Command.SINGLE_SUCCESS;37}
  1. This method may throw a Brigadier error. Brigadier catches it and shows its message to the player in red, so you never need a try here.
  2. The Brigadier way to say "this needs a player". If the executor is a player, you get it. If not (the console, for example), it throws an error with the message "A player is required to run this command here".
  3. The actual healing lives in one shared method, used by both stations.
HealCommand.javaCompiles on Paper 26.3Compile Lab
Where this file livescommands-brigadiersrcmainjavacomexamplecommandsbrigadierHealCommand.java

The package com.example.commandsbrigadier is the folder path com/example/commandsbrigadier inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • commands-brigadier/
    • src/main/
      • java/com/example/commandsbrigadier/Package com.example.commandsbrigadier
        • CommandsBrigadierPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • GameModeCommand.javaCommand (Brigadier tree)
        • HealCommand.javayou are hereCommand (Brigadier tree)
        • ToolsCommand.javaCommand (Brigadier tree)
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

39private static int healTargets(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {40    List<Player> targets = context.getArgument("targets", PlayerSelectorArgumentResolver.class)41        .resolve(context.getSource());42    for (Player target : targets) {43        heal(target);44        target.sendRichMessage("<green>You have been healed.");45    }46    context.getSource().getSender().sendRichMessage("<green>Healed <white><count></white> player(s).",47        Placeholder.unparsed("count", String.valueOf(targets.size())));48    return targets.size();49}
  1. Gets the resolver for the targets argument.
  2. Turns the name or selector into real players. If nobody matches, it throws an error ("No player was found"), so the list is never empty.
  3. Heals each player in the list and tells them.
  4. Returns how many players were healed, following the convention from above.
HealCommand.javaCompiles on Paper 26.3Compile Lab
Where this file livescommands-brigadiersrcmainjavacomexamplecommandsbrigadierHealCommand.java

The package com.example.commandsbrigadier is the folder path com/example/commandsbrigadier inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • commands-brigadier/
    • src/main/
      • java/com/example/commandsbrigadier/Package com.example.commandsbrigadier
        • CommandsBrigadierPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • GameModeCommand.javaCommand (Brigadier tree)
        • HealCommand.javayou are hereCommand (Brigadier tree)
        • ToolsCommand.javaCommand (Brigadier tree)
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

51private static void heal(Player player) {52    AttributeInstance maxHealth = player.getAttribute(Attribute.MAX_HEALTH);53    player.setHealth(maxHealth == null ? 20.0 : maxHealth.getValue());54    player.setFoodLevel(20);55    player.setSaturation(20.0f);56    player.setFireTicks(0);57}
  1. Players can have more or less than 20 health (10 hearts) if a plugin or effect changed it. Reading the attribute heals them to their real maximum. See Working with players.
  2. Fills the hunger bar (20 is full). setSaturation on the next line adds a hidden food buffer, so the bar does not start draining right away.
  3. Puts out fire, so they do not burn right after healing.

What an admin sees after /heal @a with three players online, and what each healed player sees:

Healed 3 player(s).
You have been healed.

Example: /gm <mode> [player]

A shortcut for /gamemode. The mode is required, the player is optional, and changing someone else's mode needs an extra permission. This time the stations use lambdas with a few lines inside, instead of method references:

GameModeCommand.javaCompiles on Paper 26.3Compile Lab
Where this file livescommands-brigadiersrcmainjavacomexamplecommandsbrigadierGameModeCommand.java

The package com.example.commandsbrigadier is the folder path com/example/commandsbrigadier inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • commands-brigadier/
    • src/main/
      • java/com/example/commandsbrigadier/Package com.example.commandsbrigadier
        • CommandsBrigadierPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • GameModeCommand.javayou are hereCommand (Brigadier tree)
        • HealCommand.javaCommand (Brigadier tree)
        • ToolsCommand.javaCommand (Brigadier tree)
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

21static LiteralCommandNode<CommandSourceStack> create() {22    return Commands.literal("gm")23        .requires(source -> source.getSender().hasPermission("commandsbrigadier.gamemode"))24        .then(Commands.argument("mode", ArgumentTypes.gameMode())25            .executes(context -> {26                Player player = context.getSource().getPlayerOrThrow();27                return setMode(context, player);28            })29            .then(Commands.argument("player", ArgumentTypes.player())30                .requires(source -> source.getSender().hasPermission("commandsbrigadier.gamemode.others"))31                .executes(context -> {32                    Player target = context.getArgument("player", PlayerSelectorArgumentResolver.class)33                        .resolve(context.getSource())34                        .getFirst();35                    return setMode(context, target);36                })))37        .build();38}
  1. The root has no executes, so plain /gm is incomplete. A mode is required.
  2. The same argument vanilla /gamemode uses. The game suggests survival, creative, adventure and spectator.
  3. Without a player argument, the target is whoever ran the command.
  4. Exactly one player. @a is rejected here by the game, because it could mean many.
  5. The resolver always gives a list, even for player(). It holds exactly one player, so take the first.
  6. Both stations call the same method and return what it returns.
GameModeCommand.javaCompiles on Paper 26.3Compile Lab
Where this file livescommands-brigadiersrcmainjavacomexamplecommandsbrigadierGameModeCommand.java

The package com.example.commandsbrigadier is the folder path com/example/commandsbrigadier inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • commands-brigadier/
    • src/main/
      • java/com/example/commandsbrigadier/Package com.example.commandsbrigadier
        • CommandsBrigadierPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • GameModeCommand.javayou are hereCommand (Brigadier tree)
        • HealCommand.javaCommand (Brigadier tree)
        • ToolsCommand.javaCommand (Brigadier tree)
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

40private static int setMode(CommandContext<CommandSourceStack> context, Player target) {41    GameMode mode = context.getArgument("mode", GameMode.class);42    target.setGameMode(mode);43    Component modeName = Component.translatable(mode);44    target.sendRichMessage("<gray>Your game mode is now <gold><mode></gold>.",45        Placeholder.component("mode", modeName));46    CommandSender sender = context.getSource().getSender();47    if (sender != target) {48        sender.sendRichMessage("<gray>Set <white><player></white> to <gold><mode></gold>.",49            Placeholder.unparsed("player", target.getName()),50            Placeholder.component("mode", modeName));51    }52    return Command.SINGLE_SUCCESS;53}
  1. Reads the mode argument. It is a real GameMode value, so you can pass it straight to setGameMode.
  2. The game mode's name in each player's own language: "Creative Mode" in English. See Text and colors.
  3. Only send a second message when someone changed another player's mode. Otherwise the player would get two messages about the same thing.
Your game mode is now Creative Mode.
Set Alex to Survival Mode.

Subcommands: /tools

A subcommand is a literal under the root: the give in /tools give. In Brigadier, subcommands are just more then(Commands.literal(...)) branches, each with its own children, permissions and suggestions. No if (args[0].equals("give")) chains needed. Here is the tree you are about to read:

tools runs shows help color give needs permission clear runs restricted + permission <name> runs word, custom suggestions <item> runs itemStack() <amount> runs integer(1, 64) literal: a fixed word argument: a value the player types has executes
The /tools tree. Blue nodes are literals, purple nodes are arguments, and green tags mark nodes where the command may end.
ToolsCommand.javaCompiles on Paper 26.3Compile Lab
Where this file livescommands-brigadiersrcmainjavacomexamplecommandsbrigadierToolsCommand.java

The package com.example.commandsbrigadier is the folder path com/example/commandsbrigadier inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • commands-brigadier/
    • src/main/
      • java/com/example/commandsbrigadier/Package com.example.commandsbrigadier
        • CommandsBrigadierPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • GameModeCommand.javaCommand (Brigadier tree)
        • HealCommand.javaCommand (Brigadier tree)
        • ToolsCommand.javayou are hereCommand (Brigadier tree)
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

33static LiteralCommandNode<CommandSourceStack> create() {34    return Commands.literal("tools")35        .executes(ToolsCommand::help)36        .then(Commands.literal("color")37            .then(Commands.argument("name", StringArgumentType.word())38                .suggests(ToolsCommand::suggestColors)39                .executes(ToolsCommand::color)))40        .then(Commands.literal("give")41            .requires(source -> source.getSender().hasPermission("commandsbrigadier.tools.give"))42            .then(Commands.argument("item", ArgumentTypes.itemStack())43                .executes(context -> give(context, 1))44                .then(Commands.argument("amount", IntegerArgumentType.integer(1, 64))45                    .executes(context -> give(context, IntegerArgumentType.getInteger(context, "amount"))))))46        .then(Commands.literal("clear")47            .requires(Commands.restricted(source -> source.getSender().hasPermission("commandsbrigadier.tools.clear")))48            .executes(ToolsCommand::clear))49        .build();50}
  1. Plain /tools shows a help list. Giving the root an executes is a friendly habit: players who type only the command name learn what it can do.
  2. The first subcommand. It is a literal, so the player types color exactly.
  3. Any single word. The game cannot know which words are colors, so this argument gets custom suggestions and its own check.
  4. Uses your method to fill the suggestion list for this argument.
  5. The second subcommand, with its own permission on the literal. Players without it do not even see give as an option.
  6. The same item syntax as vanilla /give, with suggestions for every item.
  7. An optional amount after the item. Both nodes have executes, so /tools give diamond and /tools give diamond 16 both work.
  8. Wraps the permission check. A restricted command asks the player to confirm when it is triggered from a clickable chat message, so nobody can trick them into emptying their inventory with a link.

Plain /tools shows this:

Tools
/tools color [name] preview a chat color
/tools give [item] [amount] give yourself an item
/tools clear empty your inventory

The give station shows how normal Paper code continues once Brigadier hands you the values:

ToolsCommand.javaCompiles on Paper 26.3Compile Lab
Where this file livescommands-brigadiersrcmainjavacomexamplecommandsbrigadierToolsCommand.java

The package com.example.commandsbrigadier is the folder path com/example/commandsbrigadier inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • commands-brigadier/
    • src/main/
      • java/com/example/commandsbrigadier/Package com.example.commandsbrigadier
        • CommandsBrigadierPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • GameModeCommand.javaCommand (Brigadier tree)
        • HealCommand.javaCommand (Brigadier tree)
        • ToolsCommand.javayou are hereCommand (Brigadier tree)
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

83private static int give(CommandContext<CommandSourceStack> context, int amount) throws CommandSyntaxException {84    Player player = context.getSource().getPlayerOrThrow();85    ItemStack item = context.getArgument("item", ItemStack.class).clone();86    item.setAmount(Math.min(amount, item.getMaxStackSize()));87    for (ItemStack leftover : player.getInventory().addItem(item).values()) {88        player.getWorld().dropItemNaturally(player.getLocation(), leftover);89    }90    player.sendRichMessage("<green>Gave you <white><amount>x</white> <item>.",91        Placeholder.unparsed("amount", String.valueOf(item.getAmount())),92        Placeholder.component("item", item.effectiveName()));93    return Command.SINGLE_SUCCESS;94}
  1. The item the player described. clone() makes your own copy, so changing its amount cannot affect anything else.
  2. Ender pearls stack to 16 and swords to 1. This keeps the amount within one stack.
  3. Puts the item in the inventory. Whatever did not fit comes back, and the loop drops it at the player's feet. See Inventories.
  4. The item's name as a component, translated for each player.
Gave you 16x Diamond.

Custom suggestions

Built-in types bring their own suggestions. For a plain word() argument you can add yours with .suggests(...). The suggestion method gets the context and a SuggestionsBuilder, adds words to the builder, and returns it:

ToolsCommand.javaCompiles on Paper 26.3Compile Lab
Where this file livescommands-brigadiersrcmainjavacomexamplecommandsbrigadierToolsCommand.java

The package com.example.commandsbrigadier is the folder path com/example/commandsbrigadier inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • commands-brigadier/
    • src/main/
      • java/com/example/commandsbrigadier/Package com.example.commandsbrigadier
        • CommandsBrigadierPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • GameModeCommand.javaCommand (Brigadier tree)
        • HealCommand.javaCommand (Brigadier tree)
        • ToolsCommand.javayou are hereCommand (Brigadier tree)
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

61private static CompletableFuture<Suggestions> suggestColors(CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) {62    String typed = builder.getRemainingLowerCase();63    for (String name : NamedTextColor.NAMES.keys()) {64        if (name.startsWith(typed)) {65            NamedTextColor color = NamedTextColor.NAMES.value(name);66            builder.suggest(name, MessageComponentSerializer.message().serialize(67                Component.text("This is " + name, color)));68        }69    }70    return builder.buildFuture();71}
  1. The return type Brigadier expects. It looks scary, but you always end with return builder.buildFuture();, so you can copy it.
  2. What the player has typed of this argument so far, in lower case. An empty string if they have typed nothing yet.
  3. The names of Minecraft's 16 chat colors, such as red and dark_aqua.
  4. Only suggest names that match what was typed. The suggestion list stays short and useful.
  5. Adds one suggestion. The second value is an optional tooltip, shown when the player hovers the suggestion. Here it is a sample of the color itself.
  6. Brigadier does not understand Adventure components, so this turns your component into Brigadier's message type.

The suggestion method runs every time the player types a character, so you can compute the list from anything that is true right now: online players, loaded worlds, a player's saved homes, entries in your config. This one suggests the names of the loaded worlds:

Dynamic suggestions
1.suggests((context, builder) -> {2    for (World world : Bukkit.getWorlds()) {3        builder.suggest(world.getName());4    }5    return builder.buildFuture();6})

Permissions with requires

.requires(...) takes a small test, a lambda that gets the source and answers true or false. If it answers false, that node and everything below it does not exist for that sender: it is missing from tab completion, and typing it gives "Unknown or incomplete command". You saw three uses:

  • On the root (/heal, /gm): the whole command needs a permission.
  • On an argument (/heal <targets>, /gm <mode> <player>): the basic command is allowed, but the stronger form needs a second permission.
  • On a subcommand (/tools give): one branch needs a permission, the rest of /tools does not.

The permissions themselves are declared in plugin.yml, so Paper knows who has them by default:

plugin.ymlCompiles on Paper 26.3Compile Lab
Where this file livescommands-brigadiersrcmainresourcesplugin.yml

Files in src/main/resources are copied into the jar exactly as they are. Paper looks for plugin.yml at the top of the jar.

  • commands-brigadier/
    • src/main/
      • java/com/example/commandsbrigadier/Package com.example.commandsbrigadier
        • CommandsBrigadierPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • GameModeCommand.javaCommand (Brigadier tree)
        • HealCommand.javaCommand (Brigadier tree)
        • ToolsCommand.javaCommand (Brigadier tree)
      • resources/Files copied into the jar as they are
        • plugin.ymlyou are hereTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

1name: CommandsBrigadier2version: '1.0.0'3main: com.example.commandsbrigadier.CommandsBrigadierPlugin4api-version: '26.3'5description: Brigadier command trees, /heal, /gm and /tools.6permissions:7  commandsbrigadier.heal:8    description: Lets a player heal themselves with /heal.9    default: op10  commandsbrigadier.heal.others:11    description: Lets a player heal other players with /heal targets.12    default: op13    children:14      commandsbrigadier.heal: true15  commandsbrigadier.gamemode:16    description: Lets a player change their own game mode with /gm.17    default: op18  commandsbrigadier.gamemode.others:19    description: Lets a player change another player's game mode.20    default: op21    children:22      commandsbrigadier.gamemode: true23  commandsbrigadier.tools.give:24    description: Lets a player give themselves items with /tools give.25    default: op26  commandsbrigadier.tools.clear:27    description: Lets a player empty their own inventory with /tools clear.28    default: true
  1. One entry per permission. The description helps server owners understand what it allows.
  2. Anyone with commandsbrigadier.heal.others automatically gets commandsbrigadier.heal too. It would be odd to heal others but not yourself.
  3. Everyone has this one unless a server owner takes it away. Clearing your own inventory hurts nobody else.

Keep requires tests fast and simple, such as permission checks, because the game asks them often: whenever it sends the command list to a player, and whenever someone runs or tab-completes the command. The command list is sent when a player joins. If permissions change while a player is online, call player.updateCommands() so their game gets the new list; permission plugins such as LuckPerms do that for you. More in Permissions.

Error messages

Brigadier errors are a CommandSyntaxException. Most of them you never write yourself: the argument types create them. Players see these before they press Enter, in red under the chat box:

Integer must not be more than 64, found 100
No player was found
Unknown item 'minecraft:diamnd'
A player is required to run this command here

For problems the game cannot know about, such as a word that is not a color, you throw your own. First describe the error once, as a field:

ToolsCommand.javaCompiles on Paper 26.3Compile Lab
Where this file livescommands-brigadiersrcmainjavacomexamplecommandsbrigadierToolsCommand.java

The package com.example.commandsbrigadier is the folder path com/example/commandsbrigadier inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • commands-brigadier/
    • src/main/
      • java/com/example/commandsbrigadier/Package com.example.commandsbrigadier
        • CommandsBrigadierPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • GameModeCommand.javaCommand (Brigadier tree)
        • HealCommand.javaCommand (Brigadier tree)
        • ToolsCommand.javayou are hereCommand (Brigadier tree)
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

27private static final DynamicCommandExceptionType UNKNOWN_COLOR = new DynamicCommandExceptionType(name ->28    MessageComponentSerializer.message().serialize(Component.text("Unknown color: " + name)));
  1. An error type with a blank to fill in, here the unknown name. For an error that never changes, use SimpleCommandExceptionType instead.
  2. A lambda that builds the message from the value you pass in later.

Then throw it where the check fails:

ToolsCommand.javaCompiles on Paper 26.3Compile Lab
Where this file livescommands-brigadiersrcmainjavacomexamplecommandsbrigadierToolsCommand.java

The package com.example.commandsbrigadier is the folder path com/example/commandsbrigadier inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • commands-brigadier/
    • src/main/
      • java/com/example/commandsbrigadier/Package com.example.commandsbrigadier
        • CommandsBrigadierPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • GameModeCommand.javaCommand (Brigadier tree)
        • HealCommand.javaCommand (Brigadier tree)
        • ToolsCommand.javayou are hereCommand (Brigadier tree)
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

73private static int color(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {74    String name = StringArgumentType.getString(context, "name").toLowerCase(Locale.ROOT);75    NamedTextColor color = NamedTextColor.NAMES.value(name);76    if (color == null) {77        throw UNKNOWN_COLOR.create(name);78    }79    context.getSource().getSender().sendMessage(Component.text("This text is " + name + ".", color));80    return Command.SINGLE_SUCCESS;81}
  1. The word the player typed, as text.
  2. Looks up the color by name. If the word is not a color, the answer is null.
  3. Creates the error with the typed name filled in and throws it. The method stops here, and Brigadier shows the message in red.
  4. The happy path: a line of text in the chosen color.
This text is aqua.
Unknown color: purplee

Why throw an error instead of sending a red message and returning? To the player both look the same, but a thrown error also tells the game that the command failed, which command blocks and /execute can react to. A good habit: throw errors for "this input is wrong", and send normal messages for everything else.

  • commands-brigadier/
    • src/
      • main/
        • java/
          • com/example/commandsbrigadier/
            • CommandsBrigadierPlugin.javaRegisters the three trees in the COMMANDS lifecycle event
            • HealCommand.java/heal and /heal targets, with two permissions
            • GameModeCommand.java/gm mode and /gm mode player
            • ToolsCommand.java/tools with color, give and clear subcommands
        • resources/
          • plugin.ymlName, main class and six permissions

Download the demo plugin or open it in the Compile Lab.

Mistakes to avoid

  • Brackets in the wrong place. An executes or then attached to the wrong node is the most common Brigadier bug. Count brackets, and keep one node per line.
  • Mismatched argument names. Commands.argument("amount", ...) and getInteger(context, "ammount") compile fine, then fail when the command runs. Copy names, do not retype them.
  • Forgetting .build(). The registrar wants a finished node. Without build() you get a compile error about LiteralArgumentBuilder.
  • Asking for an argument the station does not have. At the /roll station there is no sides yet; reading it there throws an error. Each station can only read the arguments on its own path.
  • A greedy string that is not last. Nothing after it can ever be typed.
  • Trusting suggestions. Players can type anything. Check custom values again in executes.
Try it

Launch pad

Write a /launch [targets] [power] command that throws players straight up into the air. Plain /launch launches you with power 1.0, /launch @a launches everyone, and /launch Steve 3 launches Steve with power 3. The power must be between 0.1 and 5. Everything needs the permission launchpad.launch.

Hint 1

You need three stations: the root, the targets argument (ArgumentTypes.players()), and the power argument (DoubleArgumentType.doubleArg(0.1, 5.0)) under targets.

Hint 2

To throw a player up: target.setVelocity(new Vector(0, power, 0));. Vector is org.bukkit.util.Vector.

Hint 3

Write one method launch(context, List<Player> targets, double power) that all three stations call. For the root station, the list is List.of(context.getSource().getPlayerOrThrow()).

Show the solution

One shared launch method does the work, and a small helper resolves the targets so the two lower stations do not repeat that code:

LaunchCommand.javaCompiles on Paper 26.3Compile Lab
Where this file livescommands-brigadier-exercisesrcmainjavacomexamplecommandsbrigadierexerciseLaunchCommand.java

The package com.example.commandsbrigadierexercise is the folder path com/example/commandsbrigadierexercise inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • commands-brigadier-exercise/
    • src/main/
      • java/com/example/commandsbrigadierexercise/Package com.example.commandsbrigadierexercise
        • LaunchCommand.javayou are hereCommand (Brigadier tree)
        • LaunchPadPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

1package com.example.commandsbrigadierexercise;2 3import com.mojang.brigadier.arguments.DoubleArgumentType;4import com.mojang.brigadier.context.CommandContext;5import com.mojang.brigadier.exceptions.CommandSyntaxException;6import com.mojang.brigadier.tree.LiteralCommandNode;7import io.papermc.paper.command.brigadier.CommandSourceStack;8import io.papermc.paper.command.brigadier.Commands;9import io.papermc.paper.command.brigadier.argument.ArgumentTypes;10import io.papermc.paper.command.brigadier.argument.resolvers.selector.PlayerSelectorArgumentResolver;11import java.util.List;12import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;13import org.bukkit.entity.Player;14import org.bukkit.util.Vector;15 16final class LaunchCommand {17 18    private LaunchCommand() {19    }20 21    static LiteralCommandNode<CommandSourceStack> create() {22        return Commands.literal("launch")23            .requires(source -> source.getSender().hasPermission("launchpad.launch"))24            .executes(context -> launch(context, List.of(context.getSource().getPlayerOrThrow()), 1.0))25            .then(Commands.argument("targets", ArgumentTypes.players())26                .executes(context -> launch(context, targets(context), 1.0))27                .then(Commands.argument("power", DoubleArgumentType.doubleArg(0.1, 5.0))28                    .executes(context -> launch(context, targets(context),29                        DoubleArgumentType.getDouble(context, "power")))))30            .build();31    }32 33    private static List<Player> targets(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {34        return context.getArgument("targets", PlayerSelectorArgumentResolver.class).resolve(context.getSource());35    }36 37    private static int launch(CommandContext<CommandSourceStack> context, List<Player> targets, double power) {38        for (Player target : targets) {39            target.setVelocity(new Vector(0, power, 0));40            target.sendRichMessage("<aqua>Whoosh!");41        }42        context.getSource().getSender().sendRichMessage("<gray>Launched <white><count></white> player(s).",43            Placeholder.unparsed("count", String.valueOf(targets.size())));44        return targets.size();45    }46}
  1. One permission on the root covers the whole tree.
  2. A list with just the player who ran the command, so the shared method always gets a list.
  3. A decimal number. The game rejects 0 and 50 before your code runs.
  4. A helper that both lower stations use. It may throw, so it says throws CommandSyntaxException.
  5. Straight up: nothing sideways (x and z), power upward (y).
LaunchPadPlugin.javaCompiles on Paper 26.3Compile Lab
Where this file livescommands-brigadier-exercisesrcmainjavacomexamplecommandsbrigadierexerciseLaunchPadPlugin.java

The package com.example.commandsbrigadierexercise is the folder path com/example/commandsbrigadierexercise inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • commands-brigadier-exercise/
    • src/main/
      • java/com/example/commandsbrigadierexercise/Package com.example.commandsbrigadierexercise
        • LaunchCommand.javaCommand (Brigadier tree)
        • LaunchPadPlugin.javayou are hereMain class: Paper starts here (named as main in plugin.yml)
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

1package com.example.commandsbrigadierexercise;2 3import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;4import org.bukkit.plugin.java.JavaPlugin;5 6public final class LaunchPadPlugin extends JavaPlugin {7 8    @Override9    public void onEnable() {10        getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event ->11            event.registrar().register(LaunchCommand.create(), "Launch players into the air"));12    }13}
Try it

Add a fourth tool

Add /tools heal to the /tools tree, reusing the healing from HealCommand without copying it.

Hint

The heal(Player) method in HealCommand is private. Make it package-private (remove private) so other classes in the same package can call HealCommand.heal(player).

Show the solution

Change private static void heal(Player player) in HealCommand to static void heal(Player player). Then add one more branch to the /tools tree, next to the others:

ToolsCommand.java (new branch)
1.then(Commands.literal("heal")2    .executes(context -> {3        HealCommand.heal(context.getSource().getPlayerOrThrow());4        return Command.SINGLE_SUCCESS;5    }))

Add a line for it to the help list too, and think about whether it needs a requires.

Recap

  • A Brigadier command is a tree of literal nodes (fixed words) and argument nodes (typed values). Build it with Commands.literal, Commands.argument, .then, .executes and .build().
  • The command may end at any node with executes. Each executes returns a number, usually Command.SINGLE_SUCCESS.
  • Argument types check the input for you and give you real values. Read them with the type's helper, with context.getArgument, or through a resolver.
  • Subcommands are literals under the root. Share logic by calling the same method from several stations.
  • .suggests(...) adds custom suggestions; .requires(...) hides nodes from senders who may not use them; Commands.restricted protects sensitive commands from click tricks.
  • Throw a CommandSyntaxException (from a SimpleCommandExceptionType or DynamicCommandExceptionType) for invalid input.
  • Register trees in the LifecycleEvents.COMMANDS event.

Quick quiz

  1. In /tools give diamond 16, which parts are literal nodes?

  2. A player types /roll 500, and the argument is IntegerArgumentType.integer(2, 100). What happens?

  3. How do you get the players from an ArgumentTypes.players() argument called targets?

  4. Where can a greedyString() argument go?

  5. The /heal <targets> node has .requires(source -> source.getSender().hasPermission("commandsbrigadier.heal.others")). A player has only commandsbrigadier.heal. What can they do?

Next steps