Skip to Content
1robie

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 own

All 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

MomentWhat happens
Command tree sent to clientisMet decides visibility
Player types the commandisMet decides whether it parses
perform runsrequirements 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

Last updated on

On this page