Skip to main content
Foundation’s SimpleCommand class provides a robust framework for creating Bukkit commands with automatic registration, permission checks, cooldowns, and tab completion.

Creating a basic command

Extend SimpleCommand and implement the onCommand() method:
public class KitCommand extends SimpleCommand {

    public KitCommand() {
        super("kit|kits");
        
        setDescription("Give yourself a kit");
        setUsage("<kitName>");
        setMinArguments(1);
    }

    @Override
    protected void onCommand() {
        checkConsole();
        
        final Player player = getPlayer();
        final String kitName = args[0];
        
        // Your logic here
        tell("You received the " + kitName + " kit!");
    }
}

Registering commands

Register your command in your plugin’s onPluginStart() method:
@Override
protected void onPluginStart() {
    registerCommand(new KitCommand());
}

Key features

Permissions

By default, commands use the permission format {pluginName}.command.{label}. Customize it:
public KitCommand() {
    super("kit");
    
    setPermission("myplugin.kit.use");
    setPermissionMessage("&cYou need {permission} to use this!");
}

Cooldowns

Add cooldowns to prevent command spam:
public KitCommand() {
    super("kit");
    
    setCooldown(30, TimeUnit.SECONDS);
    setCooldownMessage("&cPlease wait {duration} seconds!");
    setCooldownBypassPermission("myplugin.kit.bypass");
}

Argument validation

setMinArguments(2);

Finding players

@Override
protected void onCommand() {
    // Find online player
    final Player target = findPlayer(args[0]);
    
    // Find offline player (async)
    findOfflinePlayer(args[0], offlinePlayer -> {
        tell("Player: " + offlinePlayer.getName());
    });
    
    // Get player or sender if no args
    final Player player = findPlayerOrSelf(0);
}

Parsing arguments

// Parse integer
final int amount = findNumber(0, "&cPlease enter a valid number!");

// With bounds
final int amount = findNumber(0, 1, 64, 
    "&cAmount must be between {min} and {max}!");

// Parse double
final double price = findNumber(Double.class, 0, 
    "&cPlease enter a valid price!");

Tab completion

Override tabComplete() to provide suggestions:
@Override
protected List<String> tabComplete() {
    
    if (args.length == 1)
        return completeLastWord("starter", "pvp", "builder");
    
    if (args.length == 2)
        return completeLastWordPlayerNames();
    
    return NO_COMPLETE;
}

Tab completion helpers

completeLastWordPlayerNames()

Convenience methods

Messaging

tell("&aKit given!");
tellSuccess("Operation successful!");
tellError("Something went wrong!");
tellWarn("Be careful!");
tellInfo("Useful information");
tellQuestion("Are you sure?");

Returning early

// Return with message
returnTell("&cYou don't have permission!");

// Return with invalid args message
returnInvalidArgs();

// Show usage
returnUsage();

Argument helpers

// Get last argument
final String last = getLastArg();

// Join args from index
final String message = joinArgs(1); // "hello world" from "/msg player hello world"

// Get range of args
final String[] range = rangeArgs(1, 3);

Subcommands

Create subcommands by extending SimpleSubCommand:
public class ReloadCommand extends SimpleSubCommand {

    public ReloadCommand(SimpleCommandGroup parent) {
        super(parent, "reload|rl");
        
        setDescription("Reload the plugin");
        setPermission("myplugin.admin.reload");
    }

    @Override
    protected void onCommand() {
        tell("&aReloading...");
        SimplePlugin.getInstance().reload();
        tellSuccess("&aPlugin reloaded!");
    }
}
Group them together:
public class AdminCommand extends SimpleCommandGroup {

    @Override
    protected void registerSubcommands() {
        registerSubcommand(new ReloadCommand(this));
        registerSubcommand(new DebugCommand(this));
    }
}

Advanced features

Multiline usage

@Override
protected String[] getMultilineUsageMessage() {
    return new String[] {
        "/kit starter - Get the starter kit",
        "/kit pvp - Get the PVP kit",
        "/kit builder - Get the builder kit"
    };
}

Custom help handling

public KitCommand() {
    super("kit");
    
    setAutoHandleHelp(false); // Disable automatic help
}

@Override
protected void onCommand() {
    if (args.length == 1 && args[0].equals("help")) {
        // Custom help logic
    }
}

Placeholder replacement

@Override
protected String replacePlaceholders(String message) {
    return super.replacePlaceholders(message)
        .replace("{player}", sender.getName())
        .replace("{world}", getPlayer().getWorld().getName());
}
All commands run on the main thread. For async operations, use Common.runAsync() or Common.runLater().
Never store player or sender references in fields! They are updated dynamically when the command executes.

Build docs developers (and LLMs) love