Documentation Index
Fetch the complete documentation index at: https://mintlify.com/PaperMC/Paper/llms.txt
Use this file to discover all available pages before exploring further.
Paper provides multiple ways to create commands. The modern approach uses the BasicCommand interface and programmatic registration, while legacy plugins can still use the Command class.
Paper plugins using paper-plugin.yml cannot declare commands in YAML. You must register commands programmatically using JavaPlugin.registerCommand().
BasicCommand Interface (Recommended)
The BasicCommand interface provides a simple, Bukkit-style approach to creating commands.
Creating a BasicCommand
From io.papermc.paper.command.brigadier.BasicCommand:
@FunctionalInterface
public interface BasicCommand {
void execute(CommandSourceStack commandSourceStack, String[] args);
default Collection<String> suggest(CommandSourceStack commandSourceStack, String[] args) {
return Collections.emptyList();
}
default boolean canUse(CommandSender sender) {
String permission = this.permission();
return permission == null || sender.hasPermission(permission);
}
default @Nullable String permission() {
return null;
}
}
Implement BasicCommand
Create a class implementing BasicCommand:package com.example.myplugin;
import io.papermc.paper.command.brigadier.BasicCommand;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import net.kyori.adventure.text.Component;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.List;
public class HelloCommand implements BasicCommand {
@Override
public void execute(@NotNull CommandSourceStack stack, @NotNull String[] args) {
if (args.length == 0) {
stack.getSender().sendMessage("Hello, world!");
} else {
String name = args[0];
stack.getSender().sendMessage("Hello, " + name + "!");
}
}
@Override
public Collection<String> suggest(@NotNull CommandSourceStack stack, @NotNull String[] args) {
if (args.length == 1) {
// Suggest online player names
return stack.getSender().getServer().getOnlinePlayers().stream()
.map(Player::getName)
.toList();
}
return List.of();
}
@Override
public String permission() {
return "myplugin.hello";
}
}
Register the Command
Register your command in the plugin’s onEnable() method:@Override
public void onEnable() {
// Simple registration
registerCommand("hello", new HelloCommand());
// With description
registerCommand("hello", "Say hello to someone", new HelloCommand());
// With description and aliases
registerCommand("hello", "Say hello to someone",
List.of("hi", "greet"), new HelloCommand());
}
From JavaPlugin.java:374-436, the registerCommand() methods:public void registerCommand(String label, BasicCommand basicCommand)
public void registerCommand(String label, @Nullable String description, BasicCommand basicCommand)
public void registerCommand(String label, Collection<String> aliases, BasicCommand basicCommand)
public void registerCommand(String label, @Nullable String description,
Collection<String> aliases, BasicCommand basicCommand)
CommandSourceStack
The CommandSourceStack (package: io.papermc.paper.command.brigadier.CommandSourceStack) provides context for command execution:
public interface CommandSourceStack {
Location getLocation(); // Where the command was executed
CommandSender getSender(); // Who executed the command
@Nullable Entity getExecutor(); // The executing entity (may differ from sender)
CommandSourceStack withLocation(Location location);
CommandSourceStack withExecutor(Entity executor);
}
Usage example:
@Override
public void execute(@NotNull CommandSourceStack stack, @NotNull String[] args) {
CommandSender sender = stack.getSender();
Location location = stack.getLocation();
Entity executor = stack.getExecutor();
if (executor instanceof Player player) {
player.sendMessage("You are at: " + location);
} else {
sender.sendMessage("Console commands not supported");
}
}
Command Class (Legacy)
For advanced use cases, you can extend the Command class directly.
Creating a Command
From org.bukkit.command.Command:
public abstract class Command {
protected Command(@NotNull String name)
protected Command(@NotNull String name, @NotNull String description,
@NotNull String usageMessage, @NotNull List<String> aliases)
public abstract boolean execute(@NotNull CommandSender sender,
@NotNull String commandLabel,
@NotNull String[] args);
public List<String> tabComplete(@NotNull CommandSender sender,
@NotNull String alias,
@NotNull String[] args)
}
Example:
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
public class TeleportCommand extends Command {
public TeleportCommand() {
super("teleport", "Teleport to coordinates", "/teleport <x> <y> <z>", List.of("tp"));
setPermission("myplugin.teleport");
}
@Override
public boolean execute(@NotNull CommandSender sender,
@NotNull String label,
@NotNull String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage("Only players can use this command");
return true;
}
if (args.length != 3) {
return false; // Shows usage message
}
try {
double x = Double.parseDouble(args[0]);
double y = Double.parseDouble(args[1]);
double z = Double.parseDouble(args[2]);
player.teleport(new Location(player.getWorld(), x, y, z));
player.sendMessage("Teleported!");
return true;
} catch (NumberFormatException e) {
sender.sendMessage("Invalid coordinates");
return false;
}
}
@Override
public @NotNull List<String> tabComplete(@NotNull CommandSender sender,
@NotNull String alias,
@NotNull String[] args) {
if (args.length <= 3) {
return List.of("~"); // Suggest relative coordinates
}
return List.of();
}
}
Tab Completion
Provide suggestions for command arguments:
BasicCommand Tab Completion
public class GamemodeCommand implements BasicCommand {
@Override
public void execute(@NotNull CommandSourceStack stack, @NotNull String[] args) {
if (args.length != 1) {
stack.getSender().sendMessage("Usage: /gamemode <mode>");
return;
}
// Execute command logic
}
@Override
public Collection<String> suggest(@NotNull CommandSourceStack stack, @NotNull String[] args) {
if (args.length == 1) {
return List.of("survival", "creative", "adventure", "spectator");
}
return List.of();
}
}
Dynamic Tab Completion
@Override
public Collection<String> suggest(@NotNull CommandSourceStack stack, @NotNull String[] args) {
if (args.length == 1) {
String input = args[0].toLowerCase();
return List.of("survival", "creative", "adventure", "spectator")
.stream()
.filter(mode -> mode.startsWith(input))
.toList();
}
return List.of();
}
Permission Checking
BasicCommand Permissions
public class AdminCommand implements BasicCommand {
@Override
public void execute(@NotNull CommandSourceStack stack, @NotNull String[] args) {
// Only executes if player has permission
stack.getSender().sendMessage("Admin action performed!");
}
@Override
public String permission() {
return "myplugin.admin"; // Required permission
}
@Override
public boolean canUse(CommandSender sender) {
// Custom permission logic
return sender.hasPermission("myplugin.admin") || sender.isOp();
}
}
Command Class Permissions
From org.bukkit.command.Command:175-225:
command.setPermission("myplugin.teleport");
if (command.testPermission(sender)) {
// Sender has permission
}
// Test silently (no message sent)
if (command.testPermissionSilent(sender)) {
// Has permission
}
Command Aliases
Register multiple aliases for a command:
@Override
public void onEnable() {
registerCommand("teleport", "Teleport to coordinates",
List.of("tp", "tele"), // Aliases
new TeleportCommand());
}
Aliases will not override already existing commands (except namespaced ones). The main command label will override existing commands.
Subcommands
Implement subcommands by parsing the first argument:
public class AdminCommand implements BasicCommand {
@Override
public void execute(@NotNull CommandSourceStack stack, @NotNull String[] args) {
if (args.length == 0) {
stack.getSender().sendMessage("Usage: /admin <reload|info|debug>");
return;
}
switch (args[0].toLowerCase()) {
case "reload" -> handleReload(stack);
case "info" -> handleInfo(stack);
case "debug" -> handleDebug(stack, args);
default -> stack.getSender().sendMessage("Unknown subcommand");
}
}
@Override
public Collection<String> suggest(@NotNull CommandSourceStack stack, @NotNull String[] args) {
if (args.length == 1) {
return List.of("reload", "info", "debug");
}
return List.of();
}
private void handleReload(CommandSourceStack stack) {
stack.getSender().sendMessage("Reloading...");
}
private void handleInfo(CommandSourceStack stack) {
stack.getSender().sendMessage("Plugin info...");
}
private void handleDebug(CommandSourceStack stack, String[] args) {
stack.getSender().sendMessage("Debug mode toggled");
}
}
Lifecycle Event Registration
Alternatively, register commands via lifecycle events:
import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;
@Override
public void onEnable() {
getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event -> {
event.registrar().register(
"mycommand",
"Command description",
List.of("alias1", "alias2"),
new MyCommand()
);
});
}
Best Practices
-
Validate input:
if (args.length < 1) {
sender.sendMessage("Usage: /command <arg>");
return;
}
-
Check sender type:
if (!(sender instanceof Player player)) {
sender.sendMessage("Only players can use this command");
return;
}
-
Use permissions:
@Override
public String permission() {
return "myplugin.command";
}
-
Provide tab completion:
@Override
public Collection<String> suggest(CommandSourceStack stack, String[] args) {
// Return relevant suggestions
}
-
Handle errors gracefully:
try {
// Command logic
} catch (Exception e) {
sender.sendMessage("An error occurred");
getLogger().severe("Command error: " + e.getMessage());
}
Do not call getCommand() in onEnable() for Paper plugins. Use registerCommand() instead.