Paper’s Brigadier API enables client-side command validation, argument type hints in the chat HUD, and rich tab completion that is displayed before the player even presses Tab. CraftCommand’s annotation processor generates complete BrigadierDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/ProjectUnified/CraftCommand/llms.txt
Use this file to discover all available pages before exploring further.
LiteralCommandNode trees at compile time — zero reflection, zero runtime code generation. When the plugin enables, PaperCommandManager hooks into Paper’s LifecycleEvents.COMMANDS event and registers each tree through the official lifecycle API, ensuring correct ordering with other plugins and avoiding the fragile CommandMap manipulation required on older platforms.
Maven Setup
Add the dependencies
craftcommand-annotations supplies the compile-time annotations. craftcommand-paper-runtime provides PaperCommandManager, the PaperCommand interface (Brigadier), and the PaperBasicCommand interface (BasicCommand fallback). Shade craftcommand-paper-runtime into your plugin JAR.Configure the annotation processor
Add
craftcommand-paper-processor to <annotationProcessorPaths>. This generates the ClassName_Paper Brigadier wrapper. If you want the BasicCommand fallback wrapper instead, replace it with craftcommand-paper-processor-basic, which generates ClassName_PaperBasic. Both can coexist in the same <annotationProcessorPaths> block.paper-plugin.yml
Paper plugins usepaper-plugin.yml instead of plugin.yml. Unlike the Bukkit platform, Paper’s lifecycle-based command registration does not require individual command entries in the YAML — the plugin just needs to declare itself as a Paper plugin.
Writing Paper Commands
TeleportCommand
TheTeleportCommand below is the real example from the CraftCommand Paper example module. The annotations and parameter types are identical to the Bukkit version — the difference is that the annotation processor generates a Brigadier LiteralCommandNode tree rather than a plain Command subclass.
BroadcastCommand
BroadcastCommand shows how a subcommand method can accept CommandSourceStack directly as the sender type — useful when you need to access Paper-specific source properties.
Registering in Your Plugin
Create aPaperCommandManager in onEnable. The constructor immediately hooks into LifecycleEvents.COMMANDS — so you must call register(...) before the lifecycle event fires, which means registering in onEnable (not in a scheduler task). There is no syncCommand() call required; the lifecycle manager handles that automatically.
PaperCommandManager registers a LifecycleEvents.COMMANDS handler in its constructor. All calls to register(...) queue up command entries; they are submitted to the Paper registrar when that lifecycle event fires. This means you must call register(...) during or before onEnable — not inside an async task or a delayed scheduler.Brigadier vs BasicCommand
PaperCommandManager.register(Object) applies the following resolution order at runtime:
Try the Brigadier wrapper (_Paper)
The manager attempts to load
ClassName_Paper — the class generated by craftcommand-paper-processor. If found, it casts to PaperCommand and registers the LiteralCommandNode tree via event.registrar().register(node, description, aliases).Fall back to BasicCommand wrapper (_PaperBasic)
If
ClassName_Paper is not on the classpath (i.e. you used craftcommand-paper-processor-basic instead), the manager loads ClassName_PaperBasic. This implements Paper’s BasicCommand interface and is registered via event.registrar().register(name, description, aliases, command).- Brigadier (_Paper)
- BasicCommand (_PaperBasic)
Generated by
craftcommand-paper-processor. Produces a full LiteralCommandNode<CommandSourceStack> tree with typed argument nodes. Clients see argument type hints (e.g. <player>, <world>) in the chat HUD and benefit from server-side argument validation before the command body runs.PaperCommandManager API
register(Object commandInstance)
Tries
ClassName_Paper (Brigadier) first, then ClassName_PaperBasic (BasicCommand). Queues the wrapper to be submitted when LifecycleEvents.COMMANDS fires.register(PaperCommand command)
Registers a pre-built
PaperCommand Brigadier wrapper directly. The wrapper’s getCommandNode(), getDescription(), and getAliases() are forwarded to Paper’s command registrar.register(PaperBasicCommand command)
Registers a pre-built
PaperBasicCommand BasicCommand wrapper directly. The wrapper’s getName(), getDescription(), and getAliases() are forwarded to Paper’s command registrar.Sender Type
On the Paper platform the internal sender type used byCommandManager is CommandSourceStack (io.papermc.paper.command.brigadier.CommandSourceStack). PaperCommandManager extends CommandManager<CommandSourceStack>, so the default error handler and any custom ErrorHandler you supply receive a CommandSourceStack as the first argument.
To reach the underlying CommandSender (or cast to Player) from a CommandSourceStack, call source.getSender():
CommandSender or Player directly as the sender parameter type — the generated wrapper resolves the correct object from the CommandSourceStack automatically. Use CommandSourceStack only when you need Paper-specific source properties (e.g. source.getExecutor() for the entity that actually triggered the command, as opposed to the sender).