Requirements & permissions
Gating commands with permissions, player-only checks, confirmation prompts and your own requirement types.
A requirement decides whether a command source may use a node. Requirements
are what setPermission and setPlayerOnly are built from.
this.setPermission("myplugin.heal"); // PermissionRequirement
this.setPlayerOnly(); // PlayerOnlyRequirement
this.addRequirement(new DuringEventRequirement<>()); // your ownAll three are available on BaseCommand and SubCommand alike, and on both
builders as .permission(...), .playerOnly() and .addRequirement(...).
They hide the command, they do not reject it
A requirement is a Brigadier requires predicate, evaluated before parsing.
A source that fails it does not see the command in tab-completion and gets
Minecraft's "Unknown command" — perform never runs, so there is no "you lack
permission" branch to write.
That is usually what you want. When you would rather explain the refusal, do the
check inside perform instead:
if (!dispatch.getSender().hasPermission("myplugin.heal.others")) {
dispatch.getSender().sendMessage("You may only heal yourself.");
return CommandResultType.FAILURE;
}Rule of thumb: requirement for "this command is not yours", in-perform
check for "not like that".
Multiple requirements are AND
Every requirement on a node must pass. There is no OR combinator — express that as one requirement:
this.addRequirement((plugin, source) ->
source.getSender().hasPermission("myplugin.admin")
|| source.getSender().hasPermission("myplugin.moderator"));Down the tree
Brigadier checks each node along the path, so a parent's requirement gates everything beneath it and a child adds to it:
// GuildCommand
this.setPermission("myplugin.guild");
// GuildAdminSubCommand
this.setPermission("myplugin.guild.admin");/guild admin needs both. A child cannot loosen its parent — if one branch
should be open to everyone, do not put a requirement on the root; put it on the
branches that need it.
Writing your own
CommandRequirement<T> has a single method:
boolean isMet(T plugin, CommandSourceStack source);One method means a lambda works wherever a small check is enough, and a class when it carries state:
public class InWorldRequirement<T extends Plugin> implements CommandRequirement<T> {
private final String worldName;
public InWorldRequirement(String worldName) {
this.worldName = worldName;
}
@Override
public boolean isMet(T plugin, CommandSourceStack source) {
return source.getLocation().getWorld().getName().equals(this.worldName);
}
}this.addRequirement(new InWorldRequirement<>("minigames"));You get the plugin instance as a parameter, so a requirement can consult your own state (a config flag, an active-event registry) without capturing anything.
Keep isMet cheap
The predicate runs for every source, on the main thread, whenever the server
rebuilds or sends the command tree — including on every player join. No database
calls, no file reads, no locks. Cache the answer in memory and have isMet read
the cached value.
setPlayerOnly still needs a null check
setPlayerOnly() keeps console out, but getSenderAsPlayer() stays
@Nullable:
this.setPlayerOnly();
...
Player player = dispatch.getSenderAsPlayer();
if (player == null) { // keep this
return CommandResultType.FAILURE;
}The requirement and the accessor are independent, and requirements move around during refactors. A null check that is never taken costs nothing; the NPE that appears when someone removes the requirement is a runtime error in production.
Confirmation for destructive commands
setRequiresConfirmation(true) marks the node restricted, wrapping its
predicate with Paper's Commands.restricted(...):
this.setPermission("myplugin.wipe");
this.setRequiresConfirmation(true);Two effects, both from vanilla — it is the mechanism behind /op:
- the command cannot be run from unattended contexts such as a chat click event,
- the client shows the player a confirmation before running it.
It works with or without other requirements. On its own the command stays open to everyone and merely gains the restricted behaviour, so pair it with a permission when you mean "dangerous and privileged".
Where requirements are evaluated
| Moment | What happens |
|---|---|
| Command tree sent to client | isMet decides visibility |
| Player types the command | isMet decides whether it parses |
perform runs | requirements are already past |
Since the tree is per-source, a permission change takes effect the next time the server sends the tree to that player. If your plugin grants permissions at runtime, expect to refresh it yourself.
Next
- Sub-commands — how a branch inherits gating
- Core concepts — sender vs executor
Last updated on