Skip to main content

Arguments

A command takes input three ways, and they are not interchangeable.

Required argumentOptional argumentFlag
Looks like/heal Notch/heal [Notch]/heal --silent
Declared withaddRequiredArgumentaddOptionalArgumentaddFlag
Positionpositional, in orderpositional, after requiredlast, after everything
Order between siblingsmattersmattersdoes not matter
Can be omittednoyesyes
Read withgetArgumentgetOptionalArgumentgetFlagValue / hasFlag
Brigadier costone nodeone branchone node per ordered subset

The short version: positional data is an argument, a modifier is a flag. If leaving it out should still run the command, it is optional or a flag.

Required arguments​

The command only executes once every required argument is supplied. Brigadier will not match the path otherwise, so there is no "missing argument" branch to handle.

public class BanCommand extends BaseCommand<MyPlugin> {

public BanCommand(MyPlugin plugin) {
super(plugin, "ban");
this.addRequiredArgument("target", ArgumentTypes.player());
this.addRequiredArgument("reason", StringArgumentType.greedyString());
}

@Override
protected CommandResultType perform(CommandDispatch<MyPlugin> dispatch) {
// Guaranteed present: the command would not have matched otherwise.
Player target = dispatch.resolvePlayer("target").orElseThrow();
String reason = dispatch.getArgument("reason", String.class);
...
}
}

/ban alone does not run this — it is not a failure you handle, the path simply does not exist.

Optional arguments​

An optional argument makes the command executable both with and without it, so perform runs in either case and has to cope with absence.

this.addOptionalArgument("target", ArgumentTypes.player());
Player target = dispatch.resolvePlayer("target") // the ARGUMENT
.orElse(dispatch.getSenderAsPlayer()); // fall back to the SENDER

Both /heal and /heal Notch work. This is the usual "act on me, or on someone I name" shape.

Reading them back​

CallReturns
getArgument(name, Type.class)The value. Use for required arguments.
getArgument(name, Type.class, fallback)The value, or fallback if absent.
getOptionalArgument(name, Type.class)Optional, empty if absent.
hasArgument(name)Presence only.

For Paper's own types there are typed resolvers, which save you the ArgumentTypes round-trip: resolvePlayer, resolvePlayers, resolveEntity, resolveEntities, resolveBlockPosition, resolveFinePosition, resolvePlayerProfiles.

Argument or sender?

resolvePlayer and getSenderAsPlayer both return a Player but are unrelated. resolvePlayer("target") looks up the argument named target; getSenderAsPlayer() returns whoever typed the command. Mixing them up is the most common mistake with this API — see Core concepts.

Why hasArgument exists​

Brigadier throws the same IllegalArgumentException for "no such argument" and "argument is a different type", so catching it around a typed lookup cannot tell an absent argument from your own type mistake. hasArgument probes with Object.class, which is assignable from anything, so it can only fail when the argument is genuinely absent.

That is also why getOptionalArgument throws rather than returning empty when the type is wrong: silently swallowing a type error would hide a bug in your code.

Ordering​

Declare in this order, because that is the order Brigadier parses:

/command <required…> [optional…] [--flags…]
  • Required before optional. An optional argument followed by a required one cannot be expressed — there would be no way to tell which value you meant.
  • Flags last, always. Flag nodes branch only into other flag nodes, never back into arguments or sub-commands. /cmd sub --verbose parses; /cmd --verbose sub does not.
  • Sub-commands and positional arguments compete. A sub-command named list and a string argument in the same position are ambiguous; Brigadier resolves literals first, so the sub-command wins and the argument becomes unreachable for that input.

Two traps​

Reserved names​

Value flags generate synthetic argument names internally, all sharing a reserved prefix. Adding an argument whose name starts with that prefix throws IllegalArgumentException at construction, rather than producing a tree where your argument and a flag's value collide.

If you change the prefix with setFlagValuePrefix, do it before adding any arguments — it throws if arguments already exist, because a late change would invalidate names that already passed validation.

A value that looks like a flag​

getOptionalArgument returns empty if the argument is absent or if its value matches a registered flag name. That is deliberate: for /heal --silent, silent must not be captured as the optional target.

The consequence is that a player genuinely named like one of your flags cannot be passed positionally. dispatch.isFlagLikeValue(...) exposes the same check if you need to explain it to the user.

Cost​

A required or optional argument adds roughly one node to the command tree. A flag adds one node per ordered subset of the whole flag set — growth faster than n!, and the tree is sent to every client on join. See Flags for the measured numbers.

If you are choosing between a fourth boolean flag and an optional argument, the argument is dramatically cheaper.