Command unregistration

The CommandAPI allows you to remove commands completely from Minecraft's command list. This includes Vanilla commands, Bukkit commands, and plugin commands.

There are three methods you might use when unregistering commands:

CommandAPI.unregister(String commandName);
CommandAPI.unregister(String commandName, boolean unregisterNamespaces);
CommandAPIBukkit.unregister(String commandName, boolean unregisterNamespaces, boolean unregisterBukkit);

To understand when and how to use these methods, you need to know a little about how Bukkit loads and sets up commands. This is basically the order of events when a Bukkit server starts:

  1. Vanilla commands are placed in the Vanilla CommandDispatcher
  2. Bukkit commands are placed in the Bukkit CommandMap
    • Given the bukkit namespace (E.g. bukkit:version)
  3. Plugins are loaded
    • onLoad is called
  4. Plugins are enabled
    • Plugin commands are read from the plugin.yml file and placed in the Bukkit CommandMap
    • Given the plugin's name as their namespace (E.g. luckperms:lp)
    • onEnable is called
    • Repeat for each plugin
  5. Bukkit's help command is registered
  6. Vanilla commands are added to the Bukkit CommandMap
    • Given the minecraft namespace (E.g. minecraft:gamemode)
  7. The server is done loading

Unregistering a command only works if it happens after the command is created. Bukkit's command system is special and has two locations where commands can exist -- either the Vanilla CommandDispatcher or the Bukkit CommandMap -- so you also need to know where your command is registered. With that in mind, here is what each of the unregister methods do:

CommandAPI.unregister(String commandName);

Unregisters a command from the Vanilla CommandDispatcher.

CommandAPI.unregister(String commandName, boolean unregisterNamespaces);

Unregisters a command from the Vanilla CommandDispatcher. If unregisterNamespaces is true, then any namespaced version of the command is also unregistered.

CommandAPIBukkit.unregister(String commandName, boolean unregisterNamespaces, boolean unregisterBukkit);

Unregisters a command from Bukkit. As before, if unregisterNamespaces is true, then any namespaced version of the command is also unregistered. If unregisterBukkit is true, then only Bukkit commands in the Bukkit CommandMap are unregistered. If unregisterBukkit is false, only commands from the Vanilla CommandDispatcher are unregistered.

To give a better idea of how and when to use these methods, the rest of this page documents how to unregister different types of commands.

Unregistering a Bukkit command - /version

/version is a command provided by Bukkit. Looking at the sequence of events above, that means it is created during step 2, before plugins are loaded in step 3. Consequently, the command will exist when our plugin's onLoad method is called, so we'll unregister it there. The same code will work in onEnable too, since step 4 is also after step 2.

Since this command exists in the Bukkit CommandMap, we'll need to use CommandAPIBukkit#unregister with unregisterBukkit set to true. We'll also remove the namespaced version -- /bukkit:version -- so unregisterNamespaces will be true. All together, the code looks like this:

@Override
public void onLoad() {
    CommandAPIBukkit.unregister("version", true, true);
}
override fun onLoad() {
    CommandAPIBukkit.unregister("version", false, true)
}

With this plugin, executing /version or /bukkit:version will give the unknown command message. Note that aliases like /ver and its namespaced version /bukkit:ver will still work. To remove aliases as well, you need to unregister each as its own command. For, /ver, that would mean calling CommandAPIBukkit.unregister("ver", true, true).

Unregistering a Vanilla command - /gamemode

/gamemode is a command provided by Vanilla Minecraft. Like the previous example, Vanilla commands are created in step 1, before plugins are loaded in step 3. For variety, we'll unregister the command in our plugin's onEnable -- step 4 -- but the same code would also work in onLoad.

Since this command exists in the Vanilla CommandDispatcher, we can use CommandAPI#unregister. That works the same as CommandAPIBukkit#unregister with unregisterBukkit set to false. We don't care about the namespace, so unregisterNamespaces will be false. That means we can use the simplest method, CommandAPI.unregister(String commandName), since it sets unregisterNamespaces to false by default. All together, the code looks like this:

@Override
public void onEnable() {
    CommandAPI.unregister("gamemode");
}
override fun onEnable() {
    CommandAPI.unregister("gamemode")
}

With this code, executing /gamemode will give the unknown command exception as expected. However, even though unregisterNamespaces was false, /minecraft:gamemode can also not be run. This happens because Vanilla commands are given their namespace in step 6, after our plugin has removed /gamemode.

When the server starts, /gamemode is created in step 2 inside the Vanilla CommandDispatcher. In step 4, our plugin is enabled and we remove the /gamemode command from that CommandDispatcher. After all the plugins enable, step 6 moves all commands in the Vanilla CommandDispatcher to the Bukkit CommandMap and gives them the minecraft namespace. Since /gamemode doesn't exist at this point, step 6 cannot create the /minecraft:gamemode command. So, even though unregisterNamespaces was false, /minecraft:gamemode doesn't exist anyway.

Example - Replacing Minecraft's /gamemode command

To replace a command, first unregister the original command, then register a new implementation for that command.

@Override
public void onEnable() {
    CommandAPI.unregister("gamemode");

    // Register our new /gamemode, with survival, creative, adventure and spectator
    new CommandAPICommand("gamemode")
        .withArguments(new MultiLiteralArgument("gamemodes", "survival", "creative", "adventure", "spectator"))
        .executes((sender, args) -> {
            // Implementation of our /gamemode command
        })
        .register();
}
override fun onEnable() {
    CommandAPI.unregister("gamemode");

    // Register our new /gamemode, with survival, creative, adventure and spectator
    CommandAPICommand("gamemode")
        .withArguments(MultiLiteralArgument("gamemodes", "survival", "creative", "adventure", "spectator"))
        .executes(CommandExecutor { sender, args ->
            // Implementation of our /gamemode command
        })
        .register()
}

Now, when /gamemode is executed, it will use the new implementation defined using the CommandAPI.

Unregistering a Plugin command - /luckperms:luckperms

The /luckperms command is provided by the Bukkit LuckPerms plugin. Plugin commands are created during step 4, immediately before calling the onEnable method of the respective plugin. In this case, unregistering the command in our own plugin's onLoad would not work, since the command wouldn't exist yet. We also have to make sure that our onEnable method is called after LuckPerm's. The best way to make sure that happens is to add LuckPerms as a depend or softdepend in our plugin's plugin.yml. You can read more about the different between depend and softdepend in Spigot's documentation, but that will look something like this:

name: MyPlugin
main: some.package.name.Main
version: 1.0
depend:
  - LuckPerms

Since plugin commands are stored in the Bukkit CommandMap, we need to use CommandAPIBukkit#unregister with unregisterBukkit set to true. For demonstration’s sake, we only want to unregister the namespaced version -- /luckperms:luckperms -- and leave /luckperms alone. To do this, give "luckperms:luckperms" as the commandName, and set unregisterNamespaces to false. All together, the code looks like this:

@Override
public void onEnable() {
    CommandAPIBukkit.unregister("luckperms:luckperms", false, true);
}
override fun onEnable() {
    CommandAPIBukkit.unregister("luckperms:luckperms", false, true)
}

Executing /luckperms will work as normal, but /luckperms:luckperms will give the unknown command message.

Unregistering a CommandAPI command

Unregistering a command created by the CommandAPI is similar to both unregistering a Vanilla command and a plugin command. Like a Vanilla command, CommandAPI commands are stored in the Vanilla CommandDispatcher, so they should be unregistered with unregisterBukkit set to false. Like plugin commands, they may be created in onEnable, so you need to make sure your plugin is enabled after the plugin that adds the command.

Unlike plugin commands, CommandAPI commands may be created in onLoad, as discussed in Command loading order. That just means you may also be able to unregister the command in you own plugin's onLoad. As always, simply make sure you unregister a command after it is created, and it will be removed properly.

For our example, let's say we want to unregister the /break command created by the Bukkit Maven Example Project for the CommandAPI. If you look at that plugin's code, you can see that it registers the /break command in it's onEnable method. Therefore, we can unregister the command in our own plugin's onEnable, making sure that our plugin will enable second by adding ExamplePlugin as a depend or softdepend.

name: MyPlugin
main: some.package.name.Main
version: 1.0
depend:
  - CommandAPI
  - ExamplePlugin

Developer's Note:

If you can't find the code where a CommandAPI command is registered or just don't have access to the code of a plugin, you can still figure out when a command is registered. If you set verbose-outputs to true in the CommandAPI's configuration, it will log command registration.

For the ExamplePlugin, setting verbose-outputs to true gives this:

[Server thread/INFO]: [ExamplePlugin] Enabling ExamplePlugin v0.0.1
[Server thread/INFO]: [CommandAPI] Registering command /break block<LocationArgument>
[Server thread/INFO]: [CommandAPI] Registering command /myeffect target<PlayerArgument> potion<PotionEffectArgument>
[Server thread/INFO]: [CommandAPI] Registering command /nbt nbt<NBTCompoundArgument>

You can see that the ExamplePlugin registers its commands when onEnable is called.

In summary, we will unregister the /break command in our plugin's onEnable. We added Example plugin to the depend list in our plugin.yml so that our onEnable method runs second. unregisterNamespaces and unregisterBukkit will be set to false, and those are the default values, so we can simply use CommandAPI.unregister(String commandName). All together, the code looks like this:

@Override
public void onEnable() {
    CommandAPI.unregister("break");
}
override fun onEnable() {
    CommandAPI.unregister("break")
}

Now, when you try to execute /break, you will just get the unknown command message as if it never existed.

Special case: Unregistering Bukkit's /help

If you look at the sequence of events at the top of this page, you might notice that Bukkit's /help command gets its own place in step 5. Unlike the other Bukkit commands, /help is special and gets registered after plugins are loaded and enabled (don't ask, I don't know why :P). That means unregistering /help in onLoad or onEnable does not work, since the command doesn't exist yet.

In order to run our unregister task after the server is enabled, we can use Bukkit's Scheduler API. There are many ways to set up and run a task, and this should work in whatever way you like. You can even give the task zero delay, since Bukkit only starts processing tasks after the server is enabled.

Since /help is in the Bukkit CommandMap, we need to use CommandAPIBukkit#unregister with unregisterBukkit set to true. We'll leave /bukkit:help alone, so unregisterNamespaces will be false. All together, we can unregister Bukkit's /help command with this code:

@Override
public void onEnable() {
    new BukkitRunnable() {
        @Override
        public void run() {
            CommandAPIBukkit.unregister("help", false, true);
        }
    }.runTaskLater(this, 0);
}
override fun onEnable() {
    object : BukkitRunnable() {
        override fun run() {
            CommandAPIBukkit.unregister("help", false, true)
        }
    }.runTaskLater(this, 0)
}

Funnily, if you try to execute /help, the server will still tell you: Unknown command. Type "/help" for help.. Luckily, unregisterNamespaces was false, so you can still use /bukkit:help to figure out your problem.

Special case: Unregistering only /minecraft:gamemode

In the earlier example for Unregistering /gamemode, even though unregisterNamespaces was false, the /minecraft:gamemode command was also not executable. As explained up there, this happens because the namespaced version of commands in the Vanilla CommandDispatcher are not created until after plugins are loaded and enabled. Since we unregistered /gamemode in onEnable, when the time came for the server to transfer Vanilla commands into the Bukkit CommandMap, it didn't know to create the minecraft:gamemode command. Consequently, this means we cannot normally remove only the /minecraft:gamemode command without also unregistering /gamemode.

Of course, it is still possible to only unregister /minecraft:gamemode and the namespaced versions of other Vanilla commands. As always, in order to unregister a command, you have to unregister after the command is created. So, we just need to unregister /minecraft:gamemode after the server is enabled. Like the previous special case, we can use Bukkit's Scheduler API to run our unregister task after the server is enabled.

While /minecraft:gamemode only exists in the Bukkit CommandMap, it is the namespaced version of the Vanilla /gamemode command, so it is considered a Vanilla command. That means unregisterBukkit should be false, which is what it defaults to when using CommandAPI#unregister. The CommandAPI understands that once the server is enabled Vanilla commands will have been copied to the CommandMap, so it will be able to find /minecraft:gamemode

Finally, unregisterNamespaces should be false, and since that's the default value we don't have to include it. All together, the code looks like this:

@Override
public void onEnable() {
    new BukkitRunnable() {
        @Override
        public void run() {
            CommandAPI.unregister("minecraft:gamemode");
        }
    }.runTaskLater(this, 0);
}
override fun onEnable() {
    object : BukkitRunnable() {
        override fun run() {
            CommandAPI.unregister("minecraft:gamemode")
        }
    }.runTaskLater(this, 0)
}

With this code, /gamemode will execute as normal, but /minecraft:gamemode will give the unknown command message.

Developer's Note:

Doing the opposite action here -- only unregistering /gamemode but keeping /minecraft:gamemode -- is not recommended. That would be the following code, where commandName is "gamemode" (or any command in the Vanilla CommandDispatcher), and unregisterNamespaces is false:

// NOT RECOMMENDED
@Override
public void onEnable() {
    new BukkitRunnable() {
        @Override
        public void run() {
            CommandAPI.unregister("gamemode");
        }
    }.runTaskLater(this, 0);
}
// NOT RECOMMENDED
override fun onEnable() {
    object : BukkitRunnable() {
        override fun run() {
            CommandAPI.unregister("gamemode")
        }
    }.runTaskLater(this, 0)
}

The expected outcome of this code is that /minecraft:gamemode would work as expected, and /gamemode would give the command not found message. However, that is only true for the player's commands. If you try to use /minecraft:gamemode in the console, it will not work properly. Specifically, while you can tab-complete the command's label, minecraft:gamemode the command's arguments will not have any suggestions. If you try to execute /minecraft:gamemode in the console, it will always tell you your command is unknown or incomplete.

The main point is that if you ever try to unregister a Vanilla command after the server is enabled, the namespaced version of that command will break for the console. To avoid this issue, always set unregisterNamespaces to true if unregisterBukkit is false when unregistering commands after the server is enabled.

@Override
public void onEnable() {
    new BukkitRunnable() {
        @Override
        public void run() {
            CommandAPI.unregister("gamemode", true);
        }
    }.runTaskLater(this, 0);
}
override fun onEnable() {
    object : BukkitRunnable() {
        override fun run() {
            CommandAPI.unregister("gamemode", true)
        }
    }.runTaskLater(this, 0)
}