Skip to main content

Argument Suggestions

Sometimes, you want to send your own suggestions to users. For this, you can use the RequiredArgumentBuilder#suggests(SuggestionProvider) method.

Examining the SuggestionProvider<S> method

The SuggestionProvider<S> interface is defined as follows:

SuggestionProvider.java
@FunctionalInterface
public interface SuggestionProvider<S> {
CompletableFuture<Suggestions> getSuggestions(final CommandContext<S> context, final SuggestionsBuilder builder) throws CommandSyntaxException;
}

Similar to the Command<S> interface, this is a functional interface, which means that instead of passing in a class which implements this interface, we can just pass a lambda statement or a method reference.

Our lambda/method requires two parameters: CommandContext<S> and SuggestionsBuilder. The lambda/method has to return a CompletableFuture<Suggestions>. In order to retrieve our return value, we can just run SuggestionsBuilder#buildFuture() (or SuggestionsBuilder#build(), if we already are inside a completeable future).

Similar to other classes or interfaces with a <S> generic parameter, for Paper, this usually is a CommandSourceStack.

A very simple lambda for our suggests method might look like this:

Commands.argument("name", StringArgumentType.word())
.suggests((ctx, builder) -> builder.buildFuture())

This suggests implementation obviously does not suggest anything, as we haven't added suggestions yet.

A suggestion builder's methods

The SuggestionsBuilder has a few methods we can use to construct our suggestions:

Input retrieval

The first type of methods we will cover are the input retrieval methods: getInput(), getStart(), getRemaining(), and getRemainingLowerCase(). The following table displays what they return with the following input typed in the chat bar: /customsuggestions Asumm13Text.

MethodReturn ValueDescription
getInput()/customsuggestions Asumm13TextThe full chat input
getStart()19The index of the forst character of the argument's input
getRemaining()Asumm13TextThe input for the current argument
getRemainingLowerCase()asumm13textThe input for the current argument, lowercased

Suggestions

The following overloads of the SuggestionBuilder#suggest method all add values that will be send to the client as suggestions, but accept difference parameters:

OverloadDescription
suggest(String)Adds a String to the suggestions
suggest(String, Message)Adds a String with a tooltip to the suggestions
suggest(int)Adds an int to the suggestions
suggest(int, Message)Adds a String with a tooltip to the suggestions

The Message interface has the following implementations: LiteralMessage, which can be used for basic, non-formatted text, and AdventureComponent, which can be constructed using an Adventure component by calling new AdventureComponent(Component).

For example, if you add a suggestion like this:

builder.suggest("suggestion", new AdventureComponent(miniMessage().deserialize("<green>Suggestion tooltip")));

It will look like this on the client:

Building

There are two methods we can use to build our suggestions. The only difference between the both are that one directly returns the finished Suggestions object, whilst the other one returns a CompletableFuture<Suggestions>.

The reason for these two methods is that the SuggestionProvider's method requires the return value to be a CompletableFuture<Suggestions>. This for once allows for constructing your suggestions asynchronously inside a CompletableFuture.supplyAsync(Supplier<Suggestions>) or synchronously directly inside our lambda/method and only returning the final Suggestions object asynchronously.

Here are the same suggestions declared in the two different ways mentioned above:

// This one allows the usage of all Paper API inside the suggests method
Commands.argument("name", StringArgumentType.word())
.suggests((ctx, builder) -> {
builder.suggest("first");
builder.suggest("second");

return builder.buildFuture();
});

// This one does not allow the usage of most Paper API inside the suggests method
Commands.argument("name", StringArgumentType.word())
.suggests((ctx, builder) -> CompletableFuture.supplyAsync(() -> {
builder.suggest("first");
builder.suggest("second");

return builder.build();
}));

Example: Suggesting amounts in a give command

In commands where you give players certain items, you oftentimes include an amount argument. We could suggest 1, 32, and 64 as common amounts for items given. This could look like this:

@NullMarked
public class SuggestionsTest {

public static LiteralCommandNode<CommandSourceStack> constructGiveItemCommand() {
// Create new command: /giveitem
return Commands.literal("giveitem")
// Requires a player executor
.requires(ctx -> ctx.getExecutor() instanceof Player)
// Declare a new ItemStack argument
.then(Commands.argument("item", ArgumentTypes.itemStack())
// Declare a new integer argument with the bounds of 1 to 99
.then(Commands.argument("stacksize", IntegerArgumentType.integer(1, 99))
.suggests(SuggestionsTest::getStackSizeSuggestions)
.executes(SuggestionsTest::executeCommandLogic)
)
)
.build();
}

private static CompletableFuture<Suggestions> getStackSizeSuggestions(final CommandContext<CommandSourceStack> ctx, final SuggestionsBuilder builder) {
// Suggest 1, 32, and 64 to the user when they reach the 'stacksize' argument
builder.suggest(1);
builder.suggest(32);
builder.suggest(64);
return builder.buildFuture();
}

private static int executeCommandLogic(final CommandContext<CommandSourceStack> ctx) {
// We know that the executor will be a player, so we can just silently return
if (!(ctx.getSource().getExecutor() instanceof Player player)) {
return Command.SINGLE_SUCCESS;
}

// Retrieve our argument values
final ItemStack item = ctx.getArgument("item", ItemStack.class);
final int amount = IntegerArgumentType.getInteger(ctx, "stacksize");

// Set the item's max stack size to fit the stack size we defined, set the amount, and give it to the player
item.setData(DataComponentTypes.MAX_STACK_SIZE, amount);
item.setAmount(amount);
player.getInventory().addItem(item);

// Send a confirmation message
player.sendRichMessage("<gold>You have been given <white><amount>x</white> <aqua><item></aqua>",
Placeholder.component("amount", Component.text(amount)),
Placeholder.component("item", Component.translatable(item).hoverEvent(item))
);
return Command.SINGLE_SUCCESS;
}
}

And here is how the argument looks in-game:

Example: Filtering by user input

If you have multiple values, it is suggested that you filter your suggestions by what the user has already put in. For this, we can declare the following, simple command as a test:

public static LiteralCommandNode<CommandSourceStack> constructStringSuggestionsCommand() {
final List<String> names = List.of("Alex", "Andreas", "Stephanie", "Sophie", "Emily");

return Commands.literal("selectname")
.then(Commands.argument("name", StringArgumentType.word())

.suggests((ctx, builder) -> {
names.stream()
.filter(entry -> entry.toLowerCase().startsWith(builder.getRemainingLowerCase()))
.forEach(builder::suggest);
return builder.buildFuture();
})

).build();
}

And as you can see in the preview below, this simple setup filters suggestions by user input, providing a smooth user experience when using your command: