344 KiB
# PaperMC Knowledge Base
## General
### Contributing
* [Contributing](Contributing) - Information on how to contribute to the PaperMC project.
### Miscellaneous
* [Using databases](Using databases) - Recommended way to store a large amount of data.
* [Debugging your plugin](Debugging your plugin) - Common ways to debug your plugin.
* [Minecraft internals](Minecraft internals) - Overview of how to use internals in your plugin.
* [Reading stacktraces](Reading stacktraces) - Basics of how to read stacktraces produced by the JVM when an exception occurs.
## API
### Command API
* [Command API](Command API) - Guide to Paper's Brigadier command API.
* **Basics**
* [Introduction](Command%20API) - A guide to Paper's Brigadier command API.
* [Command trees](Command%20trees) - An extensive guide to building up a command tree.
* [Arguments and literals](Arguments%20and%20literals) - An extensive guide to command arguments and literals.
* [Executors](Executors) - A guide to execution logic for Brigadier commands.
* [Registration](Registration) - A guide to registering Brigadier commands.
* [Requirements](Requirements) - A guide to setting requirements for commands.
* [Suggestions](Suggestions) - Documentation about defining custom argument suggestions.
* [Custom arguments](Custom%20arguments) - Guide on custom arguments.
* **Arguments**
* [Minecraft-specific](Minecraft-specific) - Everything regarding the essential Brigadier arguments.
* [Location](Location) - BlockPosition, FinePosition and World argument documentation.
* [Entities and players](Entities%20and%20players) - Player and Entity arguments documentation.
* [Registry](Registry) - Documentation for arguments retrieving registry values.
* [Paper-specific](Paper-specific) - Documentation for arguments handling miscellaneous Paper API values.
* [Enums](Enums) - Documentation for EntityAnchor, GameMode and similar enum value arguments.
* [Predicates](Predicates) - Documentation for arguments that allow value validation.
* [Adventure](Adventure) - Documentation for all arguments returning Adventure API objects.
* **Miscellaneous**
* [Basic commands](Basic%20commands) - An overview of a Bukkit-style command declaration using Brigadier.
* [Comparison](Comparison) - A comparison between Brigadier and Bukkit commands.
### Component API
* [Component API](Component%20API) - Guide to Adventure components.
* [Introduction](Component%20API) - An introduction to how components work.
* [Internationalization](Internationalization) - How to use Adventure's internationalization.
* [Audiences](Audiences) - How to use Adventure's Audiences.
* [Signed messages](Signed%20messages) - A guide to working with SignedMessage objects.
### Event API
* [Event API](Event%20API) - Guide to Paper's event system.
* [Listeners](Listeners) - Developer guide for how to listen to the broadcasted events.
* [Custom events](Custom%20events) - A guide to show you how to add custom events to your plugin.
* [Handler lists](Handler%20lists) - An explanation to what an event's HandlerList is.
* [Chat events](Chat%20events) - An outline on AsyncChatEvent and how to handle it.
### Entity API
* [Entity API](Entity%20API) - Documentation for working with entities.
* [Teleportation](Teleportation) - Guide on entity teleportation.
* [Display entities](Display%20entities) - Guide on display entities.
### Inventories
* [Inventories](Inventories) - Documentation for working with inventories.
* [Menu Type API](Menu%20Type%20API) - A guide to the Menu Type API.
* [Custom InventoryHolders](Custom%20InventoryHolders) - How to use a custom InventoryHolder to identify custom inventories.
### Lifecycle API
* [Lifecycle API](Lifecycle%20API) - Documentation for Paper's Lifecycle API.
* [Introduction](Lifecycle%20API) - An introduction to Paper's Lifecycle API.
* [Datapack discovery](Datapack%20discovery) - A guide to including datapacks in your plugin and registering them with the lifecycle API.
### Experimental
* [Data components](Data%20components) - A guide to the ItemStack data component API.
* [Persistent data container (PDC)](Persistent%20data%20container%20(PDC)) - A guide to the PDC API for storing data.
* [Scheduling](Scheduling) - A guide on how to use BukkitScheduler to run code at specific times.
* [Plugin messaging](Plugin%20messaging) - How to communicate with clients or proxies.
* [Plugin configuration](Plugin%20configuration) - How to create configuration files for your plugins to customize behavior.
* [Registries](Registries) - A guide to registries and their modification on Paper.
* [Dialog API](Dialog%20API) - A guide to the dialog API introduced in 1.21.7.
* [Recipes](Recipes) - How to create and manage recipes.
* [Particles](Particles) - A comprehensive guide to particle spawning.
## Development
* [Development](Development) - Welcome to the Paper development guide!
* [Getting started](Development#getting-started)
* [Paper plugins](Paper%20plugins) - A development guide for how to write Paper-specific plugins.
* [Project setup](Project%20setup) - Step-by-step instructions on how to set up a plugin development environment.
* [How plugins work](How%20plugins%20work) - How plugins work in Paper.
* [plugin.yml](plugin.yml) - A guide to Bukkit's plugin.yml file.
* [paperweight-userdev](paperweight-userdev) - A guide on how to use the paperweight-userdev Gradle plugin to access internal code.
## Command API - Arguments
### Minecraft-specific Arguments
#### Location Argument
* **Block position argument**
* Used for retrieving the position of a block. Works the same way as the first argument of the `/setblock <position> <block>` Vanilla command.
* Returns a `BlockPosition` variable from the `BlockPositionResolver`.
* Example Usage:
```java
public static LiteralCommandNode<CommandSourceStack> blockPositionArgument() {
return Commands.literal("blockpositionargument")
.then(Commands.argument("arg", ArgumentTypes.blockPosition())
.executes(ctx -> {
final BlockPositionResolver blockPositionResolver = ctx.getArgument("arg", BlockPositionResolver.class);
final BlockPosition blockPosition = blockPositionResolver.resolve(ctx.getSource());
ctx.getSource().getSender().sendPlainMessage(
"Put in " + blockPosition.x() + " " + blockPosition.y() + " " + blockPosition.z());
return Command.SINGLE_SUCCESS;
}))
.build();
}
```
* **Fine position argument**
* Works similarly to the block position argument, with the difference being that it can accept decimal (precise) location input.
* The optional overload (`ArgumentTypes.finePosition(boolean centerIntegers)`), which defaults to false if not set, will center whole input, meaning 5 becomes 5.5 (5.0 would stay as 5.0 though), as that is the “middle” of a block. This only applies to X/Z. The y coordinate is untouched by this operation.
* Returns a `FinePositionResolver`. You can resolve that by running `FinePositionResolver#resolve(CommandSourceStack)` to get the resulting `FinePosition`.
* Example Usage:
```java
public static LiteralCommandNode<CommandSourceStack> finePositionArgument() {
return Commands.literal("fineposition")
.then(Commands.argument("arg", ArgumentTypes.finePosition(true))
.executes(ctx -> {
final FinePositionResolver resolver = ctx.getArgument("arg", FinePositionResolver.class);
final FinePosition finePosition = resolver.resolve(ctx.getSource());
ctx.getSource().getSender().sendRichMessage(
"Position: <red><x></red> <green><y></green> <blue><z></blue>",
Placeholder.unparsed("x", Double.toString(finePosition.x())),
Placeholder.unparsed("y", Double.toString(finePosition.y())),
Placeholder.unparsed("z", Double.toString(finePosition.z()))
);
return Command.SINGLE_SUCCESS;
}))
.build();
}
```
* **World argument**
* Allows the user to select one of the currently loaded worlds.
* Returns a generic Bukkit `World` object.
* Example Usage:
```java
public static LiteralCommandNode<CommandSourceStack> worldArgument() {
return Commands.literal("teleport-to-world")
.then(Commands.argument("world", ArgumentTypes.world())
.executes(ctx -> {
final World world = ctx.getArgument("world", World.class);
if (ctx.getSource().getExecutor() instanceof Player player) {
player.teleport(world.getSpawnLocation(), PlayerTeleportEvent.TeleportCause.COMMAND);
ctx.getSource().getSender().sendRichMessage(
"Successfully teleported <player> to <aqua><world></aqua>",
Placeholder.component("player", player.name()),
Placeholder.unparsed("world", world.getName())
);
return Command.SINGLE_SUCCESS;
}
ctx.getSource().getSender().sendRichMessage("<red>This command requires a player!");
return Command.SINGLE_SUCCESS;
}))
.build();
}
```
#### Entities and players Argument
* The arguments described in this section relate to arguments which you can use to retrieve entities. Their main usage is the selection of command targets.
* All of these have entity selectors (`@a`, `@e`, `@n`, etc.) as valid inputs, though they require the `minecraft.command.selector` permission in order to be able to be used. The specific arguments may allow or disallow certain selectors.
* Due to the permission requirement for selectors it is advised to add a `requires` statement to your command:
`.requires(ctx -> ctx.getSender().hasPermission("minecraft.command.selector"))`
* **Entity argument**
* Returns a list of exactly one entity.
* Returns an `EntitySelectorArgumentResolver`.
* Safe to call `List#getFirst()` to retrieve that entity.
* You can resolve it using `ArgumentResolver#resolve(CommandSourceStack)`
* Example usage:
```java
public static LiteralCommandNode<CommandSourceStack> entityArgument() {
return Commands.literal("entityarg")
.then(Commands.argument("arg", ArgumentTypes.entity())
.executes(ctx -> {
final EntitySelectorArgumentResolver entitySelectorArgumentResolver = ctx.getArgument("arg", EntitySelectorArgumentResolver.class);
final List<Entity> entities = entitySelectorArgumentResolver.resolve(ctx.getSource());
ctx.getSource().getSender().sendRichMessage(
"Found <green><entityname>",
Placeholder.component("entityname", entities.getFirst().name())
);
return Command.SINGLE_SUCCESS;
}))
.build();
}
```
* **Entities argument**
* Accepts any amount of entities, with the minimum amount of entities being 1.
* Returns an `EntitySelectorArgumentResolver`.
* Can be resolved using `ArgumentResolver#resolve(CommandSourceStack)`, which returns a `List<Entity>`.
* Example usage:
```java
public static LiteralCommandNode<CommandSourceStack> entitiesArgument() {
return Commands.literal("entitiesarg")
.then(Commands.argument("arg", ArgumentTypes.entities())
.executes(ctx -> {
final EntitySelectorArgumentResolver entitySelectorArgumentResolver = ctx.getArgument("arg", EntitySelectorArgumentResolver.class);
final List<Entity> entities = entitySelectorArgumentResolver.resolve(ctx.getSource());
final Component foundEntities = Component.join(JoinConfiguration.commas(true),
entities.stream().map(Entity::name).toList());
ctx.getSource().getSender().sendRichMessage(
"Found <green><entitynames>",
Placeholder.component("entitynames", foundEntities)
);
return Command.SINGLE_SUCCESS;
}))
.build();
}
```
* **Player argument**
* Allows to retrieve a `PlayerSelectorArgumentResolver` for player arguments.
* For this “single player” argument, you can safely get the target player by running `PlayerSelectorArgumentResolver.resolve(ctx.getSource()).getFirst()`, which returns a `Player` object.
* Example usage:
```java
public static LiteralCommandNode<CommandSourceStack> playerArgument() {
return Commands.literal("player")
.then(Commands.argument("target", ArgumentTypes.player())
.executes(ctx -> {
final PlayerSelectorArgumentResolver targetResolver = ctx.getArgument("target", PlayerSelectorArgumentResolver.class);
final Player target = targetResolver.resolve(ctx.getSource()).getFirst();
target.setVelocity(new Vector(0, 100, 0));
target.sendRichMessage("<rainbow>Yeeeeeeeeeet");
ctx.getSource().getSender().sendRichMessage(
"Yeeted <target>!",
Placeholder.component("target", target.name())
);
return Command.SINGLE_SUCCESS;
}))
.build();
}
```
* **Players argument**
* The “multiple players” argument works similarly to the “single player” argument, also returning a `PlayerSelectorArgumentResolver`.
* Instead of just resolving to exactly one `Player`, this one can resolve to more than just one player - which you should account for in case of using this argument.
* `PlayerSelectorArgumentResolver.resolve(ctx.getSource())` returns a `List<Player>`, which you can just iterate through.
* Example usage:
```java
public static LiteralCommandNode<CommandSourceStack> playersArgument() {
return Commands.literal("players")
.then(Commands.argument("targets", ArgumentTypes.players())
.executes(ctx -> {
final PlayerSelectorArgumentResolver targetResolver = ctx.getArgument("targets", PlayerSelectorArgumentResolver.class);
final List<Player> targets = targetResolver.resolve(ctx.getSource());
final CommandSender sender = ctx.getSource().getSender();
for (final Player target : targets) {
target.setVelocity(new Vector(0, 100, 0));
target.sendRichMessage("<rainbow>Yeeeeeeeeeet");
sender.sendRichMessage(
"Yeeted <target>!",
Placeholder.component("target", target.name())
);
}
return Command.SINGLE_SUCCESS;
}))
.build();
}
```
* **Player profiles argument**
* Can retrieve both offline and online players.
* Returns the result of the argument as a `PlayerProfileListResolver`, which resolves to a `Collection<PlayerProfile>`.
* This collection can be iterated to get the resulting profile(s). Usually, it only returns a single `PlayerProfile` if retrieving a player by name, but it can return multiple if using the entity selectors (like `@a` on online players). Thus it always makes sense to run whatever operation you want to run on all entries in the collection instead of just the first one.
* This argument will run API calls to Mojang servers in order to retrieve player information for players which have never joined the server before.
* It is suggested to resolve this argument in an asynchronous context in order to not cause any server lag.
* Example usage:
```java
public static LiteralCommandNode<CommandSourceStack> playerProfilesArgument() {
return Commands.literal("lookup")
.then(Commands.argument("profile", ArgumentTypes.playerProfiles())
.executes(ctx -> {
final PlayerProfileListResolver profilesResolver = ctx.getArgument("profile", PlayerProfileListResolver.class);
final Collection<PlayerProfile> foundProfiles = profilesResolver.resolve(ctx.getSource());
for (final PlayerProfile profile : foundProfiles) {
ctx.getSource().getSender().sendPlainMessage("Found " + profile.getName());
}
return Command.SINGLE_SUCCESS;
}))
.build();
}
```
#### Enum Argument
* **Entity anchor argument**
* Has two valid inputs: `feet` and `eyes`.
* The resulting `LookAnchor` is mainly used for methods like `Entity#lookAt(Position, LookAnchor)` or `Player#lookAt(Entity, LookAnchor, LookAnchor)`.
* Example usage:
```java
public static LiteralCommandNode<CommandSourceStack> entityAnchorArgument() {
return Commands.literal("entityanchor")
.then(Commands.argument("arg", ArgumentTypes.entityAnchor())
.executes(ctx -> {
final LookAnchor lookAnchor = ctx.getArgument("arg", LookAnchor.class);
ctx.getSource().getSender().sendRichMessage(
"You chose <aqua><anchor></aqua>!",
Placeholder.unparsed("anchor", lookAnchor.name())
);
return Command.SINGLE_SUCCESS;
}))
.build();
}
```
* **GameMode argument**
* Works the same way as the first argument of the Vanilla `/gamemode <gamemode>` command.
* Accepts any of the 4 valid game modes, returning a `GameMode` enum to use in code.
* Example usage:
```java
public static LiteralCommandNode<CommandSourceStack> gameModeArgument() {
return Commands.literal("gamemodearg")
.then(Commands.argument("arg", ArgumentTypes.gameMode())
.executes(ctx -> {
final GameMode gamemode = ctx.getArgument("arg", GameMode.class);
if (ctx.getSource().getExecutor() instanceof Player player) {
player.setGameMode(gamemode);
player.sendRichMessage(
"Your gamemode has been set to <red><gamemode></red>!",
Placeholder.component("gamemode", Component.translatable(gamemode))
);
return Command.SINGLE_SUCCESS;
}
ctx.getSource().getSender().sendPlainMessage("This command requires a player!");
return Command.SINGLE_SUCCESS;
}))
.build();
}
```
* **HeightMap argument**
* Consists of the following, valid inputs: `motion_blocking`, `motion_blocking_no_leaves`, `ocean_floor`, and `world_surface`.
* It is often used for declaring relative positioning for data packs or the `/execute positioned over <height_map>` command.
* Example usage:
```java
public static LiteralCommandNode<CommandSourceStack> heightMapArgument() {
return Commands.literal("heightmap")
.then(Commands.argument("arg", ArgumentTypes.heightMap())
.executes(ctx -> {
final HeightMap heightMap = ctx.getArgument("arg", HeightMap.class);
ctx.getSource().getSender().sendRichMessage(
"You selected <gold><selection></gold>",
Placeholder.unparsed("selection", heightMap.name())
);
return Command.SINGLE_SUCCESS;
}))
.build();
}
```
* **Scoreboard display slot argument**
* Allows you to retrieve a `DisplaySlot` enum value from the user.
* Example usage:
```java
public static LiteralCommandNode<CommandSourceStack> scoreboardDisplaySlotArgument() {
return Commands.literal("scoreboarddisplayslot")
.then(Commands.argument("slot", ArgumentTypes.scoreboardDisplaySlot())
.executes(ctx -> {
final DisplaySlot slot = ctx.getArgument("slot", DisplaySlot.class);
ctx.getSource().getSender().sendPlainMessage("You selected: " + slot.getId());
return Command.SINGLE_SUCCESS;
}))
.build();
}
```
* **Template mirror argument**
* The user has 3 valid input possibilities: `front_back`, `left_right`, and `none`.
* You can retrieve the result of the argument as a `Mirror` enum value.
* Example usage:
```java
public static LiteralCommandNode<CommandSourceStack> templateMirrorArgument() {
return Commands.literal("templatemirror")
.then(Commands.argument("mirror", ArgumentTypes.templateMirror())
.executes(ctx -> {
final Mirror mirror = ctx.getArgument("mirror", Mirror.class);
ctx.getSource().getSender().sendPlainMessage("You selected: " + mirror.name());
return Command.SINGLE_SUCCESS;
}))
.build();
}
```
* **Template rotation argument**
* The user has 4 valid input possibilities: `180`, `clockwise_90`, `counterclockwise_90`, and `none`.
* You can retrieve the result of the argument as a `StructureRotation` enum value.
* Example usage:
```java
public static LiteralCommandNode<CommandSourceStack> templateRotationArgument() {
return Commands.literal("templaterotation")
.then(Commands.argument("rotation", ArgumentTypes.templateRotation())
.executes(ctx -> {
final StructureRotation rotation = ctx.getArgument("rotation", StructureRotation.class);
ctx.getSource().getSender().sendPlainMessage("You selected: " + rotation.name());
return Command.SINGLE_SUCCESS;
}))
.build();
}
```
#### Adventure Argument
* **Component argument**
* Expects the JSON representation of a text component, making it inappropriate for general user input.
* The result is returned as an Adventure component to work with.
* Example usage:
```java
public static LiteralCommandNode<CommandSourceStack> componentArgument() {
return Commands.literal("componentargument")
.then(Commands.argument("arg", ArgumentTypes.component())
.executes(ctx -> {
final Component component = ctx.getArgument("arg", Component.class);
ctx.getSource().getSender().sendRichMessage(
"Your message: <input>",
Placeholder.component("input", component)
);
return Command.SINGLE_SUCCESS;
}))
.build();
}
```
* **Key argument**
* Allows a user to put in any artificial (namespaced) key, ensuring its validity.
* Returns a `Key`, which can be used at various other places in the Paper API.
* Example usage:
```java
public static LiteralCommandNode<CommandSourceStack> keyArgument() {
return Commands.literal("key")
.then(Commands.argument("key_input", ArgumentTypes.key())
.executes(ctx -> {
final Key key = ctx.getArgument("key_input", Key.class);
ctx.getSource().getSender().sendRichMessage(
"You put in <aqua><key></aqua>!",
Placeholder.unparsed("key", key.asString())
);
return Command.SINGLE_SUCCESS;
}))
.build();
}
```
* **Named color argument**
* Provides the user with the ability to select between the 16 built-in “named” text colors.
* Returns a `NamedTextColor`, which you can use for applying a color to components.
* Example usage:
```java
public static LiteralCommandNode<CommandSourceStack> namedColorArgument() {
return Commands.literal("namedcolor")
.then(Commands.argument("color", ArgumentTypes.namedColor())
.then(Commands.argument("message", StringArgumentType.greedyString())
.executes(ctx -> {
final NamedTextColor color = ctx.getArgument("color", NamedTextColor.class);
final String msg = StringArgumentType.getString(ctx, "message");
ctx.getSource().getSender().sendMessage(Component.text(msg).color(color));
return Command.SINGLE_SUCCESS;
})))
.build();
}
```
* **Adventure style argument**
* Similar to the component argument, this argument is not really appropriate for general user input, as it also follows the JSON format for displaying components.
* Returns its value in the form of a `Style` object.
* This can be applied to any component using `Component#style(Style)`.
* Example usage:
```java
public static LiteralCommandNode<CommandSourceStack> styleArgument() {
return Commands.literal("style")
.then(Commands.argument("style", ArgumentTypes.style())
.then(Commands.argument("message", StringArgumentType.greedyString())
.executes(ctx -> {
final Style style = ctx.getArgument("style", Style.class);
final String msg = StringArgumentType.getString(ctx, "message");
ctx.getSource().getSender().sendRichMessage(
"Your input: <input>",
Placeholder.component("input", Component.text(message).style(style))
);
return Command.SINGLE_SUCCESS;
})))
.build();
}
```
* **Signed message argument**
* Allows a player to send an argument in the form of a signed message to the server.
* Returns a `SignedMessageResolver`.
* In order to call its `#resolve` method, you have to pass in two parameters:
* The argument name
* The `CommandContext<CommandSourceStack>` object
* The resolved value is a `CompletableFuture<SignedMessage>`, whose `SignedMessage` value you can handle using `thenAccept(Consumer<T>)`.
* Example usage:
```java
public static LiteralCommandNode<CommandSourceStack> signedMessageArgument() {
return Commands.literal("signedmessage")
.then(Commands.argument("target", ArgumentTypes.player())
.then(Commands.argument("message", ArgumentTypes.signedMessage())
.executes(MinecraftArguments::executeSignedMessageCommand)))
.build();
}
private static int executeSignedMessageCommand(final CommandContext<CommandSourceStack> ctx)
throws CommandSyntaxException {
final Player target = ctx.getArgument("target", PlayerSelectorArgumentResolver.class)
.resolve(ctx.getSource())
.getFirst();
final SignedMessageResolver messageResolver = ctx.getArgument("message", SignedMessageResolver.class);
messageResolver.resolveSignedMessage("message", ctx)
.thenAccept(msg -> {
target.sendMessage(msg, ChatType.CHAT.bind(ctx.getSource().getSender().name()));
});
return Command.SINGLE_SUCCESS;
}
```
## Event API - Chat Events
### Chat Events
* The chat event has evolved a few times over the years.
* This guide will explain how to properly use the new `AsyncChatEvent` and its `ChatRenderer`.
* The `AsyncChatEvent` is an improved version of the old `AsyncPlayerChatEvent` that allows you to render chat messages individually for each player.
#### AsyncChatEvent vs ChatEvent
* The key difference between `AsyncChatEvent` and `ChatEvent` is that `AsyncChatEvent` is fired asynchronously.
* This means that it does not block the main thread and sends the chat message when the listener has completed.
* Be aware that using the Bukkit API in an asynchronous context (i.e. the event handler) is unsafe and exceptions may be thrown.
* If you need to use the Bukkit API, you can use `ChatEvent`.
* However, we recommend using `BukkitScheduler`.
#### Understanding the renderer
* The renderer is Paper’s way of allowing plugins to modify the chat message before it is sent to the player.
* This is done by using the `ChatRenderer` interface with its `ChatRenderer#render(Player, Component, Component, Audience)` method.
* Previously, this was done by using the `AsyncPlayerChatEvent` with its `AsyncPlayerChatEvent#setFormat(String)` method.
```java
ChatRenderer#render
public Component render(Player source, Component sourceDisplayName, Component message, Audience viewer) {
// ...
}
-
The
rendermethod is called when a chat message is sent to the player.source: The player that sent the message.sourceDisplayName: The display name of the player that sent the message.message: The message that was sent.viewer: The player that is receiving the message.
-
ChatRenderer.ViewerUnaware
- If your renderer does not need to know about the viewer, you can use the
ChatRenderer.ViewerUnawareinterface instead of theChatRendererinterface. - This will benefit performance as the message will only be rendered once instead of each individual player.
- If your renderer does not need to know about the viewer, you can use the
Using the renderer
- There are two ways to use the renderer:
- Implementing the
ChatRendererinterface in a class. - Using a lambda expression.
- Implementing the
- Depending on the complexity of your renderer, you may want to use one or the other.
Implementing the ChatRenderer interface
- Tell the event to use the renderer by using the
AbstractChatEvent#renderer()method.
ChatListener.java
public class ChatListener implements Listener, ChatRenderer {
// Implement the ChatRenderer and Listener interface
// Listen for the AsyncChatEvent
@EventHandler
public void onChat(AsyncChatEvent event) {
event.renderer(this);
// Tell the event to use our renderer
}
// Override the render method
@Override
public Component render(Player source, Component sourceDisplayName, Component message, Audience viewer) {
// ...
}
}
- Note: If you decide to create a separate class for your renderer, it is important to know that you don’t need to instantiate the class every time the event is called. In this case, you can use the singleton pattern to create a single instance of the class.
Using a lambda expression
ChatListener.java
public class ChatListener implements Listener {
@EventHandler
public void onChat(AsyncChatEvent event) {
event.renderer((source, sourceDisplayName, message, viewer) -> {
// ...
});
}
}
Rendering the message
- Return a new
Componentthat contains the message you want to send.
ChatListener.java
public class ChatListener implements Listener, ChatRenderer {
// Listener logic
@Override
public Component render(Player source, Component sourceDisplayName, Component message, Audience viewer) {
return sourceDisplayName.append(Component.text(": ")).append(message);
}
}
Command API Arguments
This knowledge base covers the various arguments available in the PaperMC Command API.
Minecraft-specific Arguments
The ArgumentTypes class provides access to Minecraft-specific arguments.
Quick Overview
| Method Name | Return Value | Quick Link |
|---|---|---|
blockPosition() |
BlockPositionResolver |
Block Position Argument |
blockState() |
BlockState |
Block State Argument |
component() |
Component (Kyori) |
Component Argument |
doubleRange() |
DoubleRangeProvider |
Double Range argument |
entity() |
EntitySelectorArgumentResolver |
Entity Argument |
entities() |
EntitySelectorArgumentResolver |
Entities Argument |
entityAnchor() |
LookAnchor |
Entity Anchor Argument |
finePosition(boolean centerIntegers) |
FinePositionResolver |
Fine Position Argument |
gameMode() |
GameMode |
GameMode Argument |
heightMap() |
HeightMap |
HeightMap Argument |
integerRange() |
IntegerRangeProvider |
Integer Range Argument |
itemPredicate() |
ItemStackPredicate |
Item Predicate Argument |
itemStack() |
ItemStack |
ItemStack Argument |
key() |
Key (Kyori) |
Key Argument |
namedColor() |
NamedTextColor (Kyori) |
Named Color Argument |
namespacedKey() |
NamespacedKey |
Bukkit NamespacedKey Argument |
objectiveCriteria() |
Criteria |
Objective Criteria Argument |
player() |
PlayerSelectorArgumentResolver |
Player Argument |
players() |
PlayerSelectorArgumentResolver |
Players Argument |
playerProfiles() |
PlayerProfileListResolver |
Player Profiles Argument |
resource(RegistryKey) |
(Depends on RegistryKey) |
Resource Argument |
resourceKey(RegistryKey) |
(Depends on RegistryKey) |
Resource Key Argument |
style() |
Style (Kyori) |
Style Argument |
signedMessage() |
SignedMessageResolver |
Signed Message Argument |
scoreboardDisplaySlot() |
DisplaySlot |
Scoreboard Display Slot Argument |
time(int mintime) |
Integer |
Time Argument |
templateMirror() |
Mirror |
Template Mirror Argument |
templateRotation() |
StructureRotation |
Template Rotation Argument |
uuid() |
UUID |
UUID Argument |
world() |
World |
World Argument |
Location Arguments
These arguments are used for retrieving location-based data.
Block position argument
The block position argument is used for retrieving the position of a block.
It works the same way as the first argument of the /setblock <position> <block> Vanilla command.
- To retrieve the
BlockPositionvariable from theBlockPositionResolver, it must be resolved using the command source.
Example usage
public static LiteralCommandNode<CommandSourceStack> blockPositionArgument() {
return Commands
.literal("blockpositionargument")
.then(Commands.argument("arg", ArgumentTypes.blockPosition())
.executes(ctx -> {
final BlockPositionResolver blockPositionResolver = ctx.getArgument("arg", BlockPositionResolver.class);
final BlockPosition blockPosition = blockPositionResolver.resolve(ctx.getSource());
ctx.getSource().getSender().sendPlainMessage(
"Put in " + blockPosition.x() + " " + blockPosition.y() + " " + blockPosition.z());
return Command.SINGLE_SUCCESS;
}))
.build();
}
In-game preview
Your device does not support video playback.
Fine position argument
The fine position argument works similarly to the block position argument, with the only difference being that it can accept decimal (precise) location input.
The optional overload ArgumentTypes.finePosition(boolean centerIntegers), which defaults to false if not set, will center whole input, meaning 5 becomes 5.5 (5.0 would stay as 5.0 though), as that is the “middle” of a block. This only applies to X/Z. The y coordinate is untouched by this operation.
- This argument returns a
FinePositionResolver. - Resolve by running
FinePositionResolver#resolve(CommandSourceStack)to get the resultingFinePosition.
Example usage
public static LiteralCommandNode<CommandSourceStack> finePositionArgument() {
return Commands
.literal("fineposition")
.then(Commands.argument("arg", ArgumentTypes.finePosition(true))
.executes(ctx -> {
final FinePositionResolver resolver = ctx.getArgument("arg", FinePositionResolver.class);
final FinePosition finePosition = resolver.resolve(ctx.getSource());
ctx.getSource().getSender().sendRichMessage("Position: <red><x></red> <green><y></green> <blue><z></blue>",
Placeholder.unparsed("x", Double.toString(finePosition.x())),
Placeholder.unparsed("y", Double.toString(finePosition.y())),
Placeholder.unparsed("z", Double.toString(finePosition.z())));
return Command.SINGLE_SUCCESS;
}))
.build();
}
In-game preview
Your device does not support video playback.
World argument
This argument allows the user to select one of the currently loaded world.
- You can retrieve the result as a generic Bukkit
Worldobject.
Example usage
public static LiteralCommandNode<CommandSourceStack> worldArgument() {
return Commands
.literal("teleport-to-world")
.then(Commands.argument("world", ArgumentTypes.world())
.executes(ctx -> {
final World world = ctx.getArgument("world", World.class);
if (ctx.getSource().getExecutor() instanceof Player player) {
player.teleport(world.getSpawnLocation(), PlayerTeleportEvent.TeleportCause.COMMAND);
ctx.getSource().getSender().sendRichMessage("Successfully teleported <player> to <aqua><world></aqua>",
Placeholder.component("player", player.name()),
Placeholder.unparsed("world", world.getName()));
return Command.SINGLE_SUCCESS;
}
ctx.getSource().getSender().sendRichMessage("<red>This command requires a player!</red>");
return Command.SINGLE_SUCCESS;
}))
.build();
}
In-game preview
Your device does not support video playback.
Entities and Players Arguments
These arguments are used for retrieving entities and players.
Entity argument
- This argument returns an
EntitySelectorArgumentResolver. - Use
ArgumentResolver#resolve(CommandSourceStack)to resolve the entity. - The
getFirst()method returns a singleEntityobject.
Example usage
public static LiteralCommandNode<CommandSourceStack> entityArgument() {
return Commands
.literal("entity")
.then(Commands.argument("target", ArgumentTypes.entity())
.executes(ctx -> {
final EntitySelectorArgumentResolver targetResolver = ctx.getArgument("target", EntitySelectorArgumentResolver.class);
final Entity entity = targetResolver.resolve(ctx.getSource()).getFirst();
ctx.getSource().getSender().sendRichMessage("Found <green><entityname></green>",
Placeholder.component("entityname", entity.name()));
return Command.SINGLE_SUCCESS;
}))
.build();
}
In-game preview
Your device does not support video playback.
If the executing player doesn’t have the minecraft.command.selector permission:
Your device does not support video playback.
If the executing player has the minecraft.command.selector permission:
Your device does not support video playback.
Entities argument
In contrast to the single entity argument, this multiple-entities argument accepts any amount of entities, with the minimum amount of entities being 1.
- They can be resolved using
ArgumentResolver#resolve(CommandSourceStack), which returns aList<Entity>.
Example usage
public static LiteralCommandNode<CommandSourceStack> entitiesArgument() {
return Commands
.literal("entitiesarg")
.then(Commands.argument("arg", ArgumentTypes.entities())
.executes(ctx -> {
final EntitySelectorArgumentResolver entitySelectorArgumentResolver = ctx.getArgument("arg", EntitySelectorArgumentResolver.class);
final List<Entity> entities = entitySelectorArgumentResolver.resolve(ctx.getSource());
final Component foundEntities = Component.join(JoinConfiguration.commas(true), entities.stream().map(Entity::name).toList());
ctx.getSource().getSender().sendRichMessage("Found <green><entitynames></green>",
Placeholder.component("entitynames", foundEntities));
return Command.SINGLE_SUCCESS;
}))
.build();
}
In-game preview
Your device does not support video playback.
Player argument
The player argument allows to retrieve a PlayerSelectorArgumentResolver for player arguments.
- For this “single player” argument, you can safely get the target player by running
PlayerSelectorArgumentResolver.resolve(ctx.getSource()).getFirst(), which returns aPlayerobject.
Example usage
This command yeets the targeted player into the air!
public static LiteralCommandNode<CommandSourceStack> playerArgument() {
return Commands
.literal("player")
.then(Commands.argument("target", ArgumentTypes.player())
.executes(ctx -> {
final PlayerSelectorArgumentResolver targetResolver = ctx.getArgument("target", PlayerSelectorArgumentResolver.class);
final Player target = targetResolver.resolve(ctx.getSource()).getFirst();
target.setVelocity(new Vector(0, 100, 0));
target.sendRichMessage("<rainbow>Yeeeeeeeeeet</rainbow>");
ctx.getSource().getSender().sendRichMessage("Yeeted <target>!",
Placeholder.component("target", target.name()));
return Command.SINGLE_SUCCESS;
}))
.build();
}
In-game preview
Your device does not support video playback.
Players argument
The “multiple players” argument works similarly to the “single player” argument, also returning a PlayerSelectorArgumentResolver.
- Instead of just resolving to exactly one
Player, this one can resolve to more than just one player - which you should account for in case of using this argument. PlayerSelectorArgumentResolver.resolve(ctx.getSource())returns aList<Player>, which you can just iterate through.
Example usage
Extending the “single player” yeet command to support multiple targets can look like this:
public static LiteralCommandNode<CommandSourceStack> playersArgument() {
return Commands
.literal("players")
.then(Commands.argument("targets", ArgumentTypes.players())
.executes(ctx -> {
final PlayerSelectorArgumentResolver targetResolver = ctx.getArgument("targets", PlayerSelectorArgumentResolver.class);
final List<Player> targets = targetResolver.resolve(ctx.getSource());
final CommandSender sender = ctx.getSource().getSender();
for (final Player target : targets) {
target.setVelocity(new Vector(0, 100, 0));
target.sendRichMessage("<rainbow>Yeeeeeeeeeet</rainbow>");
sender.sendRichMessage("Yeeted <target>!",
Placeholder.component("target", target.name()));
}
return Command.SINGLE_SUCCESS;
}))
.build();
}
In-game preview
Your device does not support video playback.
Player profiles argument
The player profiles argument is a very powerful argument which can retrieve both offline and online players.
- It returns the result of the argument as a
PlayerProfileListResolver, which resolves to aCollection<PlayerProfile>. - This collection can be iterated to get the resulting profile(s).
- Usually, it only returns a single
PlayerProfileif retrieving a player by name, but it can return multiple if using the entity selectors (like@aon online players). - It always makes sense to run whatever operation you want to run on all entries in the collection instead of just the first one.
- This argument will run API calls to Mojang servers in order to retrieve player information for players which have never joined the server before.
- It is suggested to resolve this argument in an asynchronous context in order to not cause any server lag.
- Sometimes, these API calls may fail.
Example usage - player lookup command
public static LiteralCommandNode<CommandSourceStack> playerProfilesArgument() {
return Commands
.literal("lookup")
.then(Commands.argument("profile", ArgumentTypes.playerProfiles())
.executes(ctx -> {
final PlayerProfileListResolver profilesResolver = ctx.getArgument("profile", PlayerProfileListResolver.class);
final Collection<PlayerProfile> foundProfiles = profilesResolver.resolve(ctx.getSource());
for (final PlayerProfile profile : foundProfiles) {
ctx.getSource().getSender().sendPlainMessage("Found " + profile.getName());
}
return Command.SINGLE_SUCCESS;
}))
.build();
}
In-game preview
Your device does not support video playback.
Registry Arguments
Registries in Minecraft hold information about item or block types, enchantments, potion effects, and more.
There are two types of registry arguments: resource and resourceKey.
resourceargument returns the parsed value.resourceKeyonly returns aTypedKey, which you can use to retrieve the value yourself.
Resource argument
- You can get a
ArgumentType<T>reference to it usingArgumentTypes.resource(RegistryKey<T>). - A selection of possible registry keys can be found below.
- They are accessed in a static context using the
RegistryKeyinterface. - Each entry in
RegistryKeyreturns aRegistryKey<T>. - The
<T>generic parameter describes the return type.
Example
public static LiteralCommandNode<CommandSourceStack> enchantmentRegistry() {
return Commands
.literal("enchants-registry")
.then(Commands.argument("enchantment", ArgumentTypes.resource(RegistryKey.ENCHANTMENT))
.executes(ctx -> {
final Enchantment enchantment = ctx.getArgument("enchantment", Enchantment.class);
if (ctx.getSource().getExecutor() instanceof Player player) {
final ItemStack stack = player.getInventory().getItemInMainHand();
stack.addUnsafeEnchantment(enchantment, 10);
ctx.getSource().getSender().sendRichMessage(
"Enchanted <player>'s <item> with <enchantment>!",
Placeholder.component("player", player.name()),
Placeholder.component("item", Component.translatable(stack)),
Placeholder.component("enchantment", enchantment.displayName(10))
);
return Command.SINGLE_SUCCESS;
}
ctx.getSource().getSender().sendRichMessage("<red>This command requires a player!</red>");
return Command.SINGLE_SUCCESS;
}))
.build();
}
In-game preview
Your device does not support video playback.
Caution
There are certain edge-cases, where this argument, due to missing registries on the client, will cause a Network Protocol Error.
Basically, the only argument where this is the case right now is with the STRUCTURE registry key.
// Registering this command will cause clients to not be able to connect to the server.
final LiteralCommandNode<CommandSourceStack> invalidRegistryArgument = Commands
.literal("registry-structure")
.then(Commands.argument("value", ArgumentTypes.resource(RegistryKey.STRUCTURE)))
.build();
Due to this fact, it is advised to only use the STRUCTURE registry key argument with a resourceKey(...) argument type and parse the values yourself.
Resource key argument
For the client, there is barely any difference between the using ArgumentTypes.resource or ArgumentTypes.resourceKey. The only difference is that using ArgumentTypes.resourceKey does not provide error checking.
The resource argument provides a much cleaner user experience, whilst the resourceKey argument has one very important use case: You get the raw TypedKey<T> returned as an argument result.
This object is particularly useful, as it provides all information required to be able to retrieve a value from a registry yourself.
Unless you have a specific reason for using the resourceKey argument over the resource one, the resource argument is preferred due to the client-side error checking and simple usability.
Direct code comparison
Here is a simple code snipped on how one could use the RegistryKey.ITEM registry with a resource argument type:
Commands
.argument("item", ArgumentTypes.resource(RegistryKey.ITEM))
.executes(ctx -> {
final ItemType item = ctx.getArgument("item", ItemType.class);
if (ctx.getSource().getExecutor() instanceof Player player) {
player.getInventory().addItem(item.createItemStack());
}
return Command.SINGLE_SUCCESS;
});
Here is the same code, using a resourceKey argument type. Instead of directly retrieving the argument using ctx.getArgument("item", TypedKey.class), we instead use the RegistryArgumentExtractor to retrieve our TypedKey<ItemType>.
Commands
.argument("item", ArgumentTypes.resourceKey(RegistryKey.ITEM))
.executes(ctx -> {
final TypedKey<ItemType> itemKey = RegistryArgumentExtractor.getTypedKey(ctx, RegistryKey.ITEM, "item");
ItemType item = RegistryAccess.registryAccess().getRegistry(itemKey.registryKey()).get(itemKey.key());
if (item == null) {
ctx.getSource().getSender().sendRichMessage("<red>Please provide a valid item!</red>");
return Command.SINGLE_SUCCESS;
}
if (ctx.getSource().getExecutor() instanceof Player player) {
player.getInventory().addItem(item.createItemStack());
}
return Command.SINGLE_SUCCESS;
});
Using a TypedKey
- In order to get the correct registry, you can run
RegistryAccess#getRegistry(RegistryKey). - In order to get a
RegistryAccess, you can just use the staticRegistryAccess.registryAccess()method. - The
RegistryKeyis retrieved usingTypedKey#registryKey(). - Now, in order to get the final value
T, you can runRegistry#get(Key), where the key can be retrieved usingTypedKey#key(). - This will return the backing instance from that resource key or null, if no value has been found.
Use case over resource argument
The main use case for this argument type is the ability to store the key (the value returned to you by TypedKey#key).
If you want to be able to store the exact user input and be able to retrieve the backed instance without much trouble, that is the way to do it.
Registry key previews
The following RegistryKeys exist:
| RegistryKeys Field | Return Value | Preview Video |
|---|---|---|
ATTRIBUTE |
Attribute |
Attribute |
BANNER_PATTERN |
PatternType |
Banner Pattern |
BIOME |
Biome |
Biome |
BLOCK |
BlockType |
Block |
CAT_VARIANT |
Cat.Type |
Cat Variant |
CHICKEN_VARIANT |
Chicken.Variant |
Chicken Variant |
COW_VARIANT |
Cow.Variant |
Cow Variant |
DAMAGE_TYPE |
DamageType |
Damage Type |
DATA_COMPONENT_TYPE |
DataComponentType |
Data Component Type |
DIALOG |
Dialog |
Dialog |
ENCHANTMENT |
Enchantment |
Enchantment |
ENTITY_TYPE |
EntityType |
Entity Type |
FLUID |
Fluid |
Fluid |
FROG_VARIANT |
Frog.Variant |
Frog Variant |
GAME_EVENT |
GameEvent |
Game Event |
INSTRUMENT |
MusicInstrument |
Instrument |
ITEM |
ItemType |
Item |
JUKEBOX_SONG |
JukeboxSong |
Jukebox Song |
MAP_DECORATION_TYPE |
MapCursor.Type |
Map Decoration Type |
MEMORY_MODULE_TYPE |
MemoryKey<?> |
Memory Module Type |
MENU |
MenuType |
Menu |
MOB_EFFECT |
PotionEffectType |
Mob effect |
PAINTING_VARIANT |
Art |
Painting variant |
PARTICLE_TYPE |
Particle |
Particle |
PIG_VARIANT |
Pig.Variant |
Pig Variant |
POTION |
PotionType |
Potion |
SOUND_EVENT |
Sound |
Sound |
STRUCTURE |
Structure |
Structure |
STRUCTURE_TYPE |
StructureType |
Structure type |
TRIM_MATERIAL |
TrimMaterial |
Trim material |
TRIM_PATTERN |
TrimPattern |
Trim pattern |
VILLAGER_PROFESSION |
Villager.Profession |
Villager Profession |
VILLAGER_TYPE |
Villager.Type |
Villager Type |
WOLF_SOUND_VARIANT |
Wolf.SoundVariant |
Wolf Sound Variant |
WOLF_VARIANT |
Wolf.Variant |
Wolf Variant |
Attribute
Your device does not support video playback.
Banner pattern
Your device does not support video playback.
Biome
Your device does not support video playback.
Block
Your device does not support video playback.
Cat variant
Your device does not support video playback.
Chicken variant
Your device does not support video playback.
Cow variant
Your device does not support video playback.
Damage type
Your device does not support video playback.
Data component type
Your device does not support video playback.
Dialog
Your device does not support video playback.
Enchantment
Your device does not support video playback.
Entity type
Your device does not support video playback.
Fluid
Your device does not support video playback.
Frog variant
Your device does not support video playback.
Game event
Your device does not support video playback.
Instrument
Your device does not support video playback.
Item
Your device does not support video playback.
Jukebox Song
Your device does not support video playback.
Map decoration type
Your device does not support video playback.
Memory module type
Your device does not support video playback.
Menu
Your device does not support video playback.
Mob effect
Your device does not support video playback.
Painting variant
Your device does not support video playback.
Particle
Your device does not support video playback.
Pig variant
Your device does not support video playback.
Potion
Your device does not support video playback.
Sound
Your device does not support video playback.
Structure
This argument kicks the client, so no preview for this one ¯_(ツ)_/¯
Structure type
Your device does not support video playback.
Trim material
Your device does not support video playback.
Trim pattern
Your device does not support video playback.
Villager profession
Your device does not support video playback.
Villager type
Your device does not support video playback.
Wolf sound variant
Your device does not support video playback.
Wolf variant
Your device does not support video playback.
Paper-specific Arguments
These arguments return objects frequently used in Paper API.
Block state argument
The block state argument can be used for getting a block type and explicit, associated data.
Example usage
public static LiteralCommandNode<CommandSourceStack> blockStateArgument() {
return Commands
.literal("blockstateargument")
.then(Commands.argument("arg", ArgumentTypes.blockState())
.executes(ctx -> {
final BlockState blockState = ctx.getArgument("arg", BlockState.class);
ctx.getSource().getSender().sendPlainMessage("You specified a " + blockState.getType() + "!");
return Command.SINGLE_SUCCESS;
}))
.build();
}
In-game preview
Your device does not support video playback.
ItemStack argument
The item stack argument is the way to retrieve an ItemStack following the same argument format as the Vanilla /give <player> <item> [<amount>] command as its second argument.
- The user may also define components to further customize the
ItemStack. - If you only require a
Material, you should instead check out the registry arguments.
Example usage
public static LiteralCommandNode<CommandSourceStack> itemStackArgument() {
return Commands
.literal("itemstack")
.then(Commands.argument("stack", ArgumentTypes.itemStack())
.executes(ctx -> {
final ItemStack itemStack = ctx.getArgument("stack", ItemStack.class);
if (ctx.getSource().getExecutor() instanceof Player player) {
player.getInventory().addItem(itemStack);
ctx.getSource().getSender().sendRichMessage("<green>Successfully gave <player> a <item></green>",
Placeholder.component("player", player.name()),
Placeholder.component("item", Component.translatable(itemStack)));
return Command.SINGLE_SUCCESS;
}
ctx.getSource().getSender().sendRichMessage("<red>This argument requires a player!</red>");
return Command.SINGLE_SUCCESS;
}))
.build();
}
In-game preview
Your device does not support video playback.
NamespacedKey argument
This argument allows the user to provide any artificial (namespaced) key.
- The return value of this argument is a
NamespacedKey, which makes it useful when dealing with Bukkit API.
Example usage
public static LiteralCommandNode<CommandSourceStack> namespacedKeyArgument() {
return Commands
.literal("namespacedkey")
.then(Commands.argument("key", ArgumentTypes.namespacedKey())
.executes(ctx -> {
final NamespacedKey key = ctx.getArgument("key", NamespacedKey.class);
ctx.getSource().getSender().sendRichMessage("You put in <aqua><key></aqua>!",
Placeholder.unparsed("key", key.toString()));
return Command.SINGLE_SUCCESS;
}))
.build();
}
In-game preview
Your device does not support video playback.
Time argument
The time argument allows the user to define a time frame, similar to the Vanilla /time <set|time> <time> time argument. The user has 4 possible ways of inputting time:
- Just as a number: This resolves to as usual ticks (
/timearg 1—> 1 tick) - With a
tsuffix: This also resolves to ticks (/timearg 1t—> 1 tick) - With a
ssuffix: This resolves to seconds, meaning multiplying the first number by 20. (/timearg 1s—> 20 ticks) - With a
dsuffix. This resolves as in-game days, meaning multiplying the first number by 24000. (/timearg 1d—> 24000 ticks)
If you choose to use this argument, it is advised to explain to the users what these suffixes mean, as real time (s suffix) is mixed with in-game time (t and d suffix).
The ArgumentTypes.time() method has one additional overload: ArgumentTypes.time(int mintime). This allows to set the minimum required amount of ticks this argument has to resolve to. By default this value is set to 0.
Example usage
public static LiteralCommandNode<CommandSourceStack> timeArgument() {
return Commands
.literal("timearg")
.then(Commands.argument("time", ArgumentTypes.time())
.executes(ctx -> {
final int timeInTicks = IntegerArgumentType.getInteger(ctx, "time");
if (ctx.getSource().getExecutor() instanceof Player player) {
player.getWorld().setFullTime(player.getWorld().getFullTime() + timeInTicks);
player.sendRichMessage("Moved time forward by " + timeInTicks + " ticks!");
return Command.SINGLE_SUCCESS;
}
ctx.getSource().getSender().sendPlainMessage("This argument requires a player!");
return Command.SINGLE_SUCCESS;
}))
.build();
}
In-game preview
Your device does not support video playback.
UUID argument
The UUID argument allows the user to input a valid UUID.
- You can retrieve that value as a
UUIDobject, which is used in various places, likeBukkit.getOfflinePlayer(UUID). - This argument is not very user-friendly, which is why it is suggested to only use this as a moderation or debug argument.
- For user input regarding offline player retrieval, the player profiles argument is preferred, as it allows by-name lookup.
Example usage - Lookup command
public static LiteralCommandNode<CommandSourceStack> uuidArgument() {
return Commands
.literal("uuid-lookup")
.then(Commands.argument("uuid", ArgumentTypes.uuid())
.executes(ctx -> {
final UUID uuid = ctx.getArgument("uuid", UUID.class);
final OfflinePlayer result = Bukkit.getOfflinePlayer(uuid);
ctx.getSource().getSender().sendRichMessage("Has <aqua><uuid></aqua> played before: <result>",
Placeholder.unparsed("uuid", uuid.toString()),
Placeholder.parsed("result", result.hasPlayedBefore() ? "<green>true</green>" : "<red>false</red>"));
return Command.SINGLE_SUCCESS;
}))
.build();
}
In-game preview
Your device does not support video playback.
Objective criteria argument
You can retrieve the argument value as a Criteria enum value, which can be used with Scoreboard objects.
Example usage
public static LiteralCommandNode<CommandSourceStack> objectiveCriteriaArgument() {
return Commands
.literal("objectivecriteria")
.then(Commands.argument("criteria", ArgumentTypes.objectiveCriteria())
.executes(ctx -> {
final Criteria criteria = ctx.getArgument("criteria", Criteria.class);
ctx.getSource().getSender().sendRichMessage("Default render type for <criteria>: <rendertype>",
Placeholder.unparsed("criteria", criteria.getName()),
Placeholder.unparsed("rendertype", criteria.getDefaultRenderType().name()));
return Command.SINGLE_SUCCESS;
}))
.build();
}
In-game preview
Your device does not support video playback.
Predicates Arguments
A predicate allows for checking for valid values. These arguments are dedicated to checking whether some sort of value is valid according to user input.
Double range argument
This argument can be used as a predicate for numbers, which require precise input.
Example usage
public static LiteralCommandNode<CommandSourceStack> doubleRangeArgument() {
return Commands
.literal("doublerange")
.then(Commands.argument("arg", ArgumentTypes.doubleRange())
.executes(ctx -> {
final DoubleRangeProvider doubleRangeProvider = ctx.getArgument("arg", DoubleRangeProvider.class);
final CommandSender sender = ctx.getSource().getSender();
for (int i = 0; i < 5; i++) {
sender.sendRich
```markdown
# Command API Knowledge Base
## Table of Contents
* [Minecraft Arguments](#minecraft-arguments)
* [Suggestions](#suggestions)
* [Command Trees](#command-trees)
* [Arguments and Literals](#arguments-and-literals)
* [Registry](#registry)
* [Executors](#executors)
## Minecraft Arguments
### Integer Range Argument
This argument only accepts integers.
#### Example Usage
```java
public
static
LiteralCommandNode<
CommandSourceStack
>
integerRangeArgument
()
{
return
Commands
.
literal
(
"
integerrange
"
)
.
then
(
Commands
.
argument
(
"
range
"
,
ArgumentTypes
.
integerRange
())
.
then
(
Commands
.
argument
(
"
tested_integer
"
,
IntegerArgumentType
.
integer
())
.
executes
(
MinecraftArguments
::
runIntegerRangeCommand
)))
.
build
()
;
}
private
static
int
runIntegerRangeCommand
(
final
CommandContext<
CommandSourceStack
>
ctx
)
{
final
IntegerRangeProvider
integerRangeProvider
=
ctx
.
getArgument
(
"
range
"
,
IntegerRangeProvider
.
class
)
;
final
int
integerToTest
=
IntegerArgumentType
.
getInteger
(
ctx,
"
tested_integer
"
)
;
if
(
integerRangeProvider
.
range
()
.
contains
(
integerToTest
)
) {
ctx
.
getSource
()
.
getSender
()
.
sendRichMessage
(
"
<aqua><input></aqua> <green>is</green> inside the specified range!
"
,
Placeholder
.
unparsed
(
"
input
"
,
Integer
.
toString
(
integerToTest
))
)
;
return
Command
.
SINGLE_SUCCESS
;
}
ctx
.
getSource
()
.
getSender
()
.
sendRichMessage
(
"
<aqua><input></aqua> <red>is not</red> inside the specified range!
"
,
Placeholder
.
unparsed
(
"
input
"
,
Integer
.
toString
(
integerToTest
))
)
;
return
Command
.
SINGLE_SUCCESS
;
}
In-Game Preview
(Video playback not supported)
Item Predicate Argument
This argument allows for checking whether an item fits some predicate. It is useful for filtering out certain items based on some criteria.
Example Usage
public
static
LiteralCommandNode<
CommandSourceStack
>
itemPredicateArgument
()
{
return
Commands
.
literal
(
"
itempredicate
"
)
.
then
(
Commands
.
argument
(
"
predicate
"
,
ArgumentTypes
.
itemPredicate
())
.
executes
(
ctx
->
{
final
ItemStackPredicate
predicate
=
ctx
.
getArgument
(
"
predicate
"
,
ItemStackPredicate
.
class
)
;
final
ItemStack
defaultWoodenSword
=
ItemType
.
WOODEN_SWORD
.
createItemStack
()
;
ctx
.
getSource
()
.
getSender
()
.
sendRichMessage
(
"
Does predicate include a default wooden sword? <result>
"
,
Placeholder
.
parsed
(
"
result
"
,
predicate
.
test
(
defaultWoodenSword
)
?
"
<green>true</green>
"
:
"
<red>false</red>
"
)
)
;
return
Command
.
SINGLE_SUCCESS
;
}
))
.
build
()
;
}
In-Game Preview
(Video playback not supported)
Suggestions
Sometimes, you want to send your own suggestions to users. For this, you can use the suggests(SuggestionProvider<CommandSourceStack>) method when declaring arguments.
Examining SuggestionProvider<S>
The SuggestionProvider<S> interface is defined as follows:
@FunctionalInterface
public
interface
SuggestionProvider
<
S
> {
CompletableFuture
<
Suggestions
>
getSuggestions
(
final
CommandContext
<
S
>
context
,
final
SuggestionsBuilder
builder
)
throws
CommandSyntaxException
;
}
- For Paper,
Sis usuallyCommandSourceStack. - It's a functional interface, so a lambda or method reference can be used.
- The lambda takes a
CommandContext<S>andSuggestionsBuilderand returns aCompletableFuture<Suggestions>.
A very simple lambda for our suggests method might look like this:
Commands
.
argument
(
"
name
"
,
StringArgumentType
.
word
())
.
suggests
(
(ctx, builder)
->
builder
.
buildFuture
())
;
This example obviously does not suggest anything, as we haven’t added any suggestions yet.
The SuggestionsBuilder
The SuggestionsBuilder has a few methods we can use to construct our suggestions:
Input Retrieval
The input retrieval methods are: getInput(), getStart(), getRemaining(), and getRemainingLowerCase().
| Method | Return Value | Description |
|---|---|---|
getInput() |
/customsuggestions Asumm13Text |
The full chat input |
getStart() |
19 |
The index of the first character of the argument’s input |
getRemaining() |
Asumm13Text |
The input for the current argument |
getRemainingLowerCase() |
asumm13text |
The input for the current argument, lowercased |
Example Input: /customsuggestions Asumm13Text
Suggestions
The following overloads of the SuggestionsBuilder#suggest method add values that will be send to the client as argument suggestions:
| Overload | Description |
|---|---|
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 an int with a tooltip to the suggestions |
There are two ways of retrieving a Message instance:
- Using
LiteralMessage, which can be used for basic, non-formatted text. - Using the
MessageComponentSerializer, which can be used to serializeComponentobjects intoMessageobjects.
For example, if you add a suggestion like this:
builder
.
suggest
(
"
suggestion
"
,
MessageComponentSerializer
.
message
()
.
serialize
(
MiniMessage
.
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 object. The only difference between those is that one directly returns the finished Suggestions object, whilst the other one returns a CompletableFuture<Suggestions>.
The reason for these two methods is that SuggestionProvider expects the return value to be CompletableFuture<Suggestions>. This for once allows for constructing your suggestions asynchronously inside a CompletableFuture.supplyAsync(Supplier<Suggestions>) statement, or synchronously directly inside our lambda and returning the final Suggestions object asynchronously.
Here are the same suggestions declared in the two different ways mentioned above:
// Here, you are safe to use all Paper API
Commands
.
argument
(
"
name
"
,
StringArgumentType
.
word
())
.
suggests
(
(ctx, builder)
->
{
builder
.
suggest
(
"
first
"
)
;
builder
.
suggest
(
"
second
"
)
;
return
builder
.
buildFuture
()
;
}
)
;
// Here, most Paper API is not usable
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 Item Command
In commands, where you give players items, you oftentimes include an amount argument. We could suggest 1, 16, 32, and 64 as common amounts for items given. The command implementation could look like this:
@NullMarked
public
class
SuggestionsTest
{
public
static
LiteralCommandNode
<
CommandSourceStack
>
constructGiveItemCommand
()
{
// Create new command: /giveitem
return
Commands
.
literal
(
"
giveitem
"
)
// Require a player to execute the command
.
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
(
"
amount
"
,
IntegerArgumentType
.
integer
(
1
,
99
))
// Here, we use method references, since otherwise, our command definition would grow too big
.
suggests
(
SuggestionsTest
::
getAmountSuggestions
)
.
executes
(
SuggestionsTest
::
executeCommandLogic
)
)
)
.
build
()
;
}
private
static
CompletableFuture
<
Suggestions
>
getAmountSuggestions
(
final
CommandContext
<
CommandSourceStack
>
ctx
,
final
SuggestionsBuilder
builder
)
{
// Suggest 1, 16, 32, and 64 to the user when they reach the 'amount' argument
builder
.
suggest
(
1
)
;
builder
.
suggest
(
16
)
;
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
;
}
// If the player has no empty slot, we tell the player that they have no free inventory space
final
int
firstEmptySlot
=
player
.
getInventory
()
.
firstEmpty
()
;
if
(firstEmptySlot
==
-
1
) {
player
.
sendRichMessage
(
"
<light_purple>You do not have enough space in your inventory!
"
)
;
return
Command
.
SINGLE_SUCCESS
;
}
// Retrieve our argument values
final
ItemStack
item
=
ctx
.
getArgument
(
"
item
"
,
ItemStack
.
class
)
;
final
int
amount
=
IntegerArgumentType
.
getInteger
(
ctx,
"
amount
"
)
;
// Set the item's amount and give it to the player
item
.
setAmount
(
amount
)
;
player
.
getInventory
()
.
setItem
(
firstEmptySlot, item
)
;
// Send a confirmation message
player
.
sendRichMessage
(
"
<light_purple>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 command looks in-game:
(Video playback not supported)
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
()
;
}
This simple setup filters suggestions by user input, providing a smooth user experience when running the command:
(Video playback not supported)
Command Trees
Command trees are the structure of Brigadier commands. This section explains how to understand and build them.
What is a Tree?
A command tree is a hierarchical structure where the base command is the "root" and subsequent arguments or subcommands are "branches."
- Root: The base command (e.g.,
/customplugin). - Branch: Arguments or subcommands following the root (e.g.,
reload,tphere).
Example:
/customplugin reload
/customplugin tphere
/customplugin killall
The root is /customplugin, and reload, tphere, and killall are branches.
How Can We Visualize A Tree In Code?
LiteralArgumentBuilder
<
CommandSourceStack
>
root
=
Commands
.
literal
(
"
customplugin
"
)
;
This defines the root of the command tree. Branches can be added using the .then(...) method.
LiteralArgumentBuilder
<
CommandSourceStack
>
root
=
Commands
.
literal
(
"
customplugin
"
)
;
root
.
then
(
Commands
.
literal
(
"
reload
"
))
;
root
.
then
(
Commands
.
literal
(
"
tphere
"
))
;
root
.
then
(
Commands
.
literal
(
"
killall
"
))
;
Here, reload, tphere, and killall are added as subcommands (child literals) of the customplugin root.
Creating a More Advanced Command
Consider the following command structure:
/advanced
┣━┳ killall
┃ ┣━━ entities
┃ ┣━━ players
┃ ┗━━ zombies
┗━┳ eat
┣━━ ice-cream
┗━━ main-dish
This allows for:
/advanced killall entities/advanced killall players/advanced killall zombies/advanced eat ice-cream/advanced eat main-dish
To create this, define the literals furthest from the root first:
LiteralArgumentBuilder
<
CommandSourceStack
>
entities
=
Commands
.
literal
(
"
entities
"
)
;
LiteralArgumentBuilder
<
CommandSourceStack
>
players
=
Commands
.
literal
(
"
players
"
)
;
LiteralArgumentBuilder
<
CommandSourceStack
>
zombies
=
Commands
.
literal
(
"
zombies
"
)
;
LiteralArgumentBuilder
<
CommandSourceStack
>
iceCream
=
Commands
.
literal
(
"
ice-cream
"
)
;
LiteralArgumentBuilder
<
CommandSourceStack
>
mainDish
=
Commands
.
literal
(
"
main-dish
"
)
;
Then, define the next layer (killall and eat):
LiteralArgumentBuilder
<
CommandSourceStack
>
killall
=
Commands
.
literal
(
"
killall
"
)
;
LiteralArgumentBuilder
<
CommandSourceStack
>
eat
=
Commands
.
literal
(
"
eat
"
)
;
Add the child elements to their parents:
killall
.
then
(
entities
)
;
killall
.
then
(
players
)
;
killall
.
then
(
zombies
)
;
eat
.
then
(
iceCream
)
;
eat
.
then
(
mainDish
)
;
Finally, create the root node and add the killall and eat subcommands:
LiteralArgumentBuilder
<
CommandSourceStack
>
advancedCommandRoot
=
Commands
.
literal
(
"
advanced
"
)
;
advancedCommandRoot
.
then
(
killall
)
;
advancedCommandRoot
.
then
(
eat
)
;
Chaining then Method Calls Together
The .then() method returns the same element it was called on, allowing for chaining:
killall
.
then
(
entities
)
.
then
(
players
)
.
then
(
zombies
)
;
This avoids storing every child node in its own variable. It can also be written as:
killall
.
then
(
Commands
.
literal
(
"
entities
"
))
.
then
(
Commands
.
literal
(
"
players
"
))
.
then
(
Commands
.
literal
(
"
zombies
"
))
;
The entire command tree can be built using chained calls:
LiteralArgumentBuilder
<
CommandSourceStack
>
advancedCommandRoot
=
Commands
.
literal
(
"
advanced
"
)
.
then
(
Commands
.
literal
(
"
eat
"
)
.
then
(
Commands
.
literal
(
"
ice-cream
"
))
.
then
(
Commands
.
literal
(
"
main-dish
"
))
)
.
then
(
Commands
.
literal
(
"
killall
"
)
.
then
(
Commands
.
literal
(
"
entities
"
))
.
then
(
Commands
.
literal
(
"
players
"
))
.
then
(
Commands
.
literal
(
"
zombies
"
))
)
;
Arguments and Literals
This section explains the difference between arguments and literals in Brigadier commands and how to use them.
Introduction
The .then(...) method of an ArgumentBuilder<CommandSourceStack, ?> takes another ArgumentBuilder<CommandSourceStack, ?> object.
Two implementations of ArgumentBuilder are:
RequiredArgumentBuilder: Created withCommands.argument(String, ArgumentType<T>).LiteralArgumentBuilder: Created withCommands.literal(String).
Argument: A variable input by the user. It's semi-unpredictable but should always return a valid entry of the object it backs.
Literal: A non-variable input by the user. Used to define predictable input, as each literal is a new branch on the command tree.
Literals
Literals cannot be accessed in code but define a specific branch in the command tree.
Commands
.
literal
(
"
plant
"
)
.
then
(
Commands
.
literal
(
"
tree
"
)
.
executes
(
ctx
->
{
/* Here we are on /plant tree */
}
)
)
.
then
(
Commands
.
literal
(
"
grass
"
)
.
executes
(
ctx
->
{
/* Here we are on /plant grass */
}
))
;
The executes method declares logic for the branch. Without executes, the branch is not executable.
Arguments
Arguments are created using Commands.argument(String, ArgumentType<T>). This returns a RequiredArgumentBuilder. The T type parameter declares the return type of the argument, which can be used inside the executes method.
Built-in argument types:
| Name | Return value | Possible Input | Description |
|---|---|---|---|
BoolArgumentType.bool() |
Boolean |
true/false |
Only allows a boolean value |
IntegerArgumentType.integer() |
Integer |
253, -123, 0 |
Any valid integer |
LongArgumentType.longArg() |
Long |
25418263123783 |
Any valid long |
FloatArgumentType.floatArg() |
Float |
253.2, -25.0 |
Any valid float |
DoubleArgumentType.doubleArg() |
Double |
4123.242, -1.1 |
Any valid double |
StringArgumentType.word() |
String |
letters-and+1234567 |
A single word. May only contain letters and numbers and these characters: +, -, _, and .. |
StringArgumentType.string() |
String |
"with spaces" |
A single word, or, if quoted, any valid string with spaces |
StringArgumentType.greedyString() |
String |
unquoted spaces |
The literal written input. May contain any characters. Has to be the last argument |
Boolean Argument Type and Argument Parsing
Commands
.
literal
(
"
serverflight
"
)
.
then
(
Commands
.
argument
(
"
allow
"
,
BoolArgumentType
.
bool
())
.
executes
(
ctx
->
{
boolean
allowed
=
ctx
.
getArgument
(
"
allow
"
,
boolean
.
class
)
;
/* Toggle server flying */
}
)
)
;
The Commands.argument(String, ArgumentType) method takes the node name. The executes lambda has a method called T getArgument(String, Class<T>). The first parameter is the name of the method we want to retrieve. The second parameter is the return value of the argument.
Number Arguments
Number arguments (IntegerArgumentType.integer(), etc.) have three overloads:
IntegerArgumentType.integer(): Any value betweenInteger.MIN_VALUEandInteger.MAX_VALUE.IntegerArgumentType.integer(int min): Any value betweenminandInteger.MAX_VALUE.IntegerArgumentType.integer(int min, int max): Any value betweenminandmax.
Example:
Commands
.
literal
(
"
flyspeed
"
)
.
then
(
Commands
.
argument
(
"
speed
"
,
FloatArgumentType
.
floatArg
(
0
,
1.0f
))
.
executes
(
ctx
->
{
float
speed
=
ctx
.
getArgument
(
"
speed
"
,
float
.
class
)
;
/* Set player's flight speed */
return
Command
.
SINGLE_SUCCESS
;
}
)
)
;
Some arguments have special ways of being retrieved:
float
speed
=
FloatArgumentType
.
getFloat
(
ctx,
"
speed
"
)
;
Parsers for Brigadier-native arguments exist:
BoolArgumentType.getBoolIntegerArgumentType.getIntegerLongArgumentType.getLongFloatArgumentType.getFloatDoubleArgumentType.getDoubleStringArgumentType.getString
Native arguments provide client-side error checking.
String Arguments
Three string arguments: word, string, and greedyString.
word: Only accepts a single word consisting of alphanumerical characters and these special characters:+,-,_, and..string: If unquoted, it follows the same rules asword. If quoted, any combination of unicode characters is allowed. Quotes can be escaped using a backslash\.greedyString: Does not perform any parsing and allows any input. Must be the last argument. Quotes are counted as literal characters.
Further Reference
Minecraft Arguments
Custom arguments are defined by Paper and accessed via the ArgumentTypes class.
Custom Arguments
Implement the CustomArgumentType<T, N> interface to define your own arguments.
Registry
Registries in Minecraft hold information like item or block types, enchantments, potion effects, and more.
Two types of registry arguments:
resource: Returns the parsed value.resourceKey: Returns aTypedKey, which can be used to retrieve the value.
Resource Argument
Get a ArgumentType<T> reference using ArgumentTypes.resource(RegistryKey<T>). Possible registry keys are accessed statically through the RegistryKey interface.
Example:
public
static
LiteralCommandNode<
CommandSourceStack
>
enchantmentRegistry
()
{
return
Commands
.
literal
(
"
enchants-registry
"
)
.
then
(
Commands
.
argument
(
"
enchantment
"
,
ArgumentTypes
.
resource
(
RegistryKey
.
ENCHANTMENT
))
.
executes
(
ctx
->
{
final
Enchantment
enchantment
=
ctx
.
getArgument
(
"
enchantment
"
,
Enchantment
.
class
)
;
if
(
ctx
.
getSource
()
.
getExecutor
()
instanceof
Player
player) {
final
ItemStack
stack
=
player
.
getInventory
()
.
getItemInMainHand
()
;
stack
.
addUnsafeEnchantment
(
enchantment,
10
)
;
ctx
.
getSource
()
.
getSender
()
.
sendRichMessage
(
"
Enchanted <player>'s <item> with <enchantment>!
"
,
Placeholder
.
component
(
"
player
"
,
player
.
name
())
,
Placeholder
.
component
(
"
item
"
,
Component
.
translatable
(
stack
))
,
Placeholder
.
component
(
"
enchantment
"
,
enchantment
.
displayName
(
10
))
)
;
return
Command
.
SINGLE_SUCCESS
;
}
ctx
.
getSource
()
.
getSender
()
.
sendRichMessage
(
"
<red>This command requires a player!
"
)
;
return
Command
.
SINGLE_SUCCESS
;
}
))
.
build
()
;
}
This defines an enchantment argument using an enchantment registry key resource and retrieves the value using ctx.getArgument("enchantment", Enchantment.class).
Caution
Using the STRUCTURE registry key with the resource argument can cause a Network Protocol Error due to missing client registries. Use resourceKey(...) instead and parse the values yourself.
Resource Key Argument
The client sees little difference between ArgumentTypes.resource and ArgumentTypes.resourceKey. The main difference is that ArgumentTypes.resourceKey does not provide error checking.
The resourceKey argument provides the raw TypedKey<T> as an argument result, which contains all the information needed to retrieve a value from a registry.
Tip
Unless there's a specific reason to use resourceKey, resource is preferred due to client-side error checking and ease of use.
Direct Code Comparison
Using RegistryKey.ITEM with a resource argument type:
Commands
.
argument
(
"
item
"
,
ArgumentTypes
.
resource
(
RegistryKey
.
ITEM
))
.
executes
(
ctx
->
{
final
ItemType
item
=
ctx
.
getArgument
(
"
item
"
,
ItemType
.
class
)
;
if
(
ctx
.
getSource
()
.
getExecutor
()
instanceof
Player
player) {
player
.
getInventory
()
.
addItem
(
item
.
createItemStack
())
;
}
return
Command
.
SINGLE_SUCCESS
;
}
)
;
Using RegistryKey.ITEM with a resourceKey argument type:
Commands
.
argument
(
"
item
"
,
ArgumentTypes
.
resourceKey
(
RegistryKey
.
ITEM
))
.
executes
(
ctx
->
{
final
TypedKey
<
ItemType
>
itemKey
=
RegistryArgumentExtractor
.
getTypedKey
(
ctx,
RegistryKey
.
ITEM
,
"
item
"
)
;
ItemType
item
=
RegistryAccess
.
registryAccess
()
.
getRegistry
(
itemKey
.
registryKey
())
.
get
(
itemKey
.
key
())
;
if
(item
==
null
) {
ctx
.
getSource
()
.
getSender
()
.
sendRichMessage
(
"
<red>Please provide a valid item!
"
)
;
return
Command
.
SINGLE_SUCCESS
;
}
if
(
ctx
.
getSource
()
.
getExecutor
()
instanceof
Player
player) {
player
.
getInventory
()
.
addItem
(
item
.
createItemStack
())
;
}
return
Command
.
SINGLE_SUCCESS
;
}
)
Using a TypedKey
- Get the correct registry using
RegistryAccess#getRegistry(RegistryKey). Get aRegistryAccessusingRegistryAccess.registryAccess(). TheRegistryKeyis retrieved usingTypedKey#registryKey(). - Get the final value
TusingRegistry#get(Key), where the key can be retrieved usingTypedKey#key(). This returns the backing instance from that resource key or null if no value is found.
Use Case Over Resource Argument
The main use case is the ability to store the key (the value returned by TypedKey#key). This allows storing the exact user input and retrieving the backed instance easily.
Registry Key Previews
| RegistryKeys Field | Return Value |
|---|---|
ATTRIBUTE |
Attribute |
BANNER_PATTERN |
PatternType |
BIOME |
Biome |
BLOCK |
BlockType |
CAT_VARIANT |
Cat.Type |
CHICKEN_VARIANT |
Chicken.Variant |
COW_VARIANT |
Cow.Variant |
DAMAGE_TYPE |
DamageType |
DATA_COMPONENT_TYPE |
DataComponentType |
DIALOG |
Dialog |
ENCHANTMENT |
Enchantment |
ENTITY_TYPE |
EntityType |
FLUID |
Fluid |
FROG_VARIANT |
Frog.Variant |
GAME_EVENT |
GameEvent |
INSTRUMENT |
MusicInstrument |
ITEM |
ItemType |
JUKEBOX_SONG |
JukeboxSong |
MAP_DECORATION_TYPE |
MapCursor.Type |
MEMORY_MODULE_TYPE |
MemoryKey<?> |
MENU |
MenuType |
MOB_EFFECT |
PotionEffectType |
PAINTING_VARIANT |
Art |
PARTICLE_TYPE |
Particle |
PIG_VARIANT |
Pig.Variant |
POTION |
PotionType |
SOUND_EVENT |
Sound |
STRUCTURE |
Structure |
STRUCTURE_TYPE |
StructureType |
TRIM_MATERIAL |
TrimMaterial |
TRIM_PATTERN |
TrimPattern |
VILLAGER_PROFESSION |
Villager.Profession |
VILLAGER_TYPE |
Villager.Type |
WOLF_SOUND_VARIANT |
Wolf.SoundVariant |
WOLF_VARIANT |
# Command API Knowledge Base
## Introduction
Paper's command system is built on top of Minecraft's Brigadier command system, providing a powerful and flexible way to define commands and arguments.
**Advantages over the Bukkit command system:**
* Less parsing or error checking required by the developer for arguments.
* Better user experience with client error checking.
* Integration with reload events, allowing commands usable in datapacks.
* Easier creation of subcommands.
**Resources:**
* [Command Tree](#command-trees)
* [Arguments and Literals](#arguments-and-literals)
* [Command Executors](#executors)
* [Command Registration](#registration)
* [Command Requirements](#requirements)
* [Argument Suggestions](#suggestions)
* [Custom Arguments](#custom-arguments)
* [Minecraft Arguments](#minecraft-specific)
**Future Pages:**
* Tutorial: Creating Utility Commands
* The Command Dispatcher
* Forks and Redirects
* Tutorial: Extending the vanilla execute command
**Additional Support:**
* Discord server: #paper-dev channel
## Command Trees
(This section's content is not available in the provided documents.)
## Arguments and Literals
(This section's content is not available in the provided documents.)
## Executors
This section covers the `executes(...)` method from the `ArgumentBuilder` class.
**Examining the `executes` method:**
```java
ArgumentBuilder.java
public T executes(Command<S> command);
The Command<S> interface is a FunctionalInterface, allowing the use of lambda statements.
Command.java
@FunctionalInterface
public interface Command<S> {
int SINGLE_SUCCESS = 1;
int run(CommandContext<S> ctx) throws CommandSyntaxException;
}
The run method in the Command<S> interface has one parameter, CommandContext<S>, and returns an integer.
CommandContext<S>: Provides information about the sender and command arguments.S getSource(): Returns the source of the command, which is always aCommandSourceStackfor theexecutesmethod.V getArgument(String, Class<V>): Retrieves arguments from the command.
CommandSourceStack methods:
Location getLocation()CommandSender getSender(): Returns the command sender.@Nullable Entity getExecutor(): Returns the command executor, relevant when using/execute as <entity> run <our_command>.
Example: Flyspeed Command
Commands.literal("flyspeed")
.then(Commands.argument("speed", FloatArgumentType.floatArg(0, 1.0f))
.executes(ctx -> {
float speed = FloatArgumentType.getFloat(ctx, "speed");
CommandSender sender = ctx.getSource().getSender();
Entity executor = ctx.getSource().getExecutor();
if (!(executor instanceof Player player)) {
sender.sendPlainMessage("Only players can fly!");
return Command.SINGLE_SUCCESS;
}
player.setFlySpeed(speed);
if (sender == executor) {
player.sendPlainMessage("Successfully set your flight speed to " + speed);
return Command.SINGLE_SUCCESS;
}
sender.sendRichMessage("Successfully set <playername>'s flight speed to " + speed,
Placeholder.component("playername", player.name()));
player.sendPlainMessage("Your flight speed has been set to " + speed);
return Command.SINGLE_SUCCESS;
})
);
Explanation:
- Defines a
/flyspeedcommand with a float argument "speed" (0-1). - Retrieves the speed argument using
FloatArgumentType.getFloat. - Retrieves the
CommandSourceStackand then the sender and executor. CommandSender: Interface implemented by entities (including players) and theConsoleCommandSender.- Checks if the executor is a
Player. - Sets the player's flight speed and sends a confirmation message.
- Handles cases where the command is executed by the player themselves or by another sender (using
/execute). - Returns
Command.SINGLE_SUCCESS(value 1) upon successful execution.
Logic Separation:
If the command logic is too large, use method references to improve readability.
public class FlightSpeedCommand {
public static LiteralArgumentBuilder<CommandSourceStack> createCommand() {
return Commands.literal("flyspeed")
.then(Commands.argument("speed", FloatArgumentType.floatArg(0, 1.0f))
.executes(FlightSpeedCommand::runFlySpeedLogic));
}
private static int runFlySpeedLogic(CommandContext<CommandSourceStack> ctx) {
float speed = FloatArgumentType.getFloat(ctx, "speed");
CommandSender sender = ctx.getSource().getSender();
Entity executor = ctx.getSource().getExecutor();
if (!(executor instanceof Player player)) {
sender.sendPlainMessage("Only players can fly!");
return Command.SINGLE_SUCCESS;
}
player.setFlySpeed(speed);
if (sender == executor) {
player.sendPlainMessage("Successfully set your flight speed to " + speed);
return Command.SINGLE_SUCCESS;
}
sender.sendRichMessage("Successfully set <playername>'s flight speed to " + speed,
Placeholder.component("playername", player.name()));
player.sendPlainMessage("Your flight speed has been set to " + speed);
return Command.SINGLE_SUCCESS;
}
}
Registration
Brigadier commands are registered using the LifecycleEventManager in Paper. This ensures commands are re-registered after server reload events.
Accessing the LifecycleEventManager:
-
Plugin Bootstrapper (Preferred): Requires
paper-plugin.yml.public class CustomPluginBootstrap implements PluginBootstrap { @Override public void bootstrap(BootstrapContext context) { context.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, commands -> { // register your commands here ... }); } }context.getLifecycleManager(): Returns aLifecycleEventManager<BootstrapContext>object.LifecycleEventManager#registerEventHandler(LifecycleEventType, LifecycleEventHandler): Registers the lifecycle event.LifecycleEvents.COMMANDS: Specifies the event type for command registration.LifecycleEventHandler: A functional interface with therunmethod. The lambda parameter is aReloadableRegistrarEvent<Commands>.ReloadableRegistrarEvent<Commands>:ReloadableRegistrarEvent.Cause cause()Commands registrar(): Provides access to theCommandsclass for registering commands.
-
Plugin Main Class:
public final class PluginMainClass extends JavaPlugin { @Override public void onEnable() { this.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, commands -> { // register your commands here ... }); } }JavaPlugin#getLifecycleManager(): Returns aLifecycleEventManager<Plugin>.- The rest of the methods work the same way as with the PluginBootstrap.
Registering Commands using the Commands Class:
The Commands class provides overloads for the Commands#register method.
Registering a LiteralCommandNode:
LiteralCommandNode<CommandSourceStack> buildCommand = Commands.literal("testcmd")
.then(Commands.literal("argument_one"))
.then(Commands.literal("argument_two"))
.build();
this.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, commands -> {
commands.registrar().register(buildCommand);
});
Registering a BasicCommand:
final BasicCommand basicCommand = ...;
this.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, commands -> {
commands.registrar().register("commandname", basicCommand);
});
Further Reference:
- [LifecycleEventManager](link not provided in the document)
Requirements
(This section's content is not available in the provided documents.)
Suggestions
This section describes how to send custom suggestions to users using the suggests(SuggestionProvider<CommandSourceStack>) method.
Examining SuggestionProvider<S>:
SuggestionProvider.java
@FunctionalInterface
public interface SuggestionProvider<S> {
CompletableFuture<Suggestions> getSuggestions(final CommandContext<S> context, final SuggestionsBuilder builder) throws CommandSyntaxException;
}
- Similar to
Command<S>, this is a functional interface. - Lambda parameters:
CommandContext<S>andSuggestionsBuilder. - Return value:
CompletableFuture<Suggestions>.
Example of a simple lambda:
Commands.argument("name", StringArgumentType.word())
.suggests((ctx, builder) -> builder.buildFuture());
The SuggestionsBuilder:
Provides methods to construct suggestions.
Input Retrieval Methods:
| Method | Return Value | Description | Example Input: /customsuggestions Asumm13Text |
|---|---|---|---|
getInput() |
/customsuggestions Asumm13Text |
The full chat input | |
getStart() |
19 |
The index of the first character of the argument's input | |
getRemaining() |
Asumm13Text |
The input for the current argument | |
getRemainingLowerCase() |
asumm13text |
The input for the current argument, lowercased |
Suggestions Methods:
| Overload | Description |
|---|---|
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 an int with a tooltip to the suggestions |
Messagecan be retrieved using:LiteralMessage: For basic, non-formatted text.MessageComponentSerializer: For serializingComponentobjects intoMessageobjects.
Example of adding a suggestion with a tooltip:
builder.suggest("suggestion", MessageComponentSerializer.message().serialize(MiniMessage.miniMessage().deserialize("<green>Suggestion tooltip")));
Building:
SuggestionsBuilder#build(): Returns the finishedSuggestionsobject directly.SuggestionsBuilder#buildFuture(): Returns aCompletableFuture<Suggestions>.
The SuggestionProvider expects a CompletableFuture<Suggestions> return value. This allows for asynchronous construction of suggestions.
Example of declaring suggestions:
// Synchronous
Commands.argument("name", StringArgumentType.word())
.suggests((ctx, builder) -> {
builder.suggest("first");
builder.suggest("second");
return builder.buildFuture();
});
// Asynchronous
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 Item Command:
@NullMarked
public class SuggestionsTest {
public static LiteralCommandNode<CommandSourceStack> constructGiveItemCommand() {
return Commands.literal("giveitem")
.requires(ctx -> ctx.getExecutor() instanceof Player)
.then(Commands.argument("item", ArgumentTypes.itemStack())
.then(Commands.argument("amount", IntegerArgumentType.integer(1, 99))
.suggests(SuggestionsTest::getAmountSuggestions)
.executes(SuggestionsTest::executeCommandLogic)))
.build();
}
private static CompletableFuture<Suggestions> getAmountSuggestions(final CommandContext<CommandSourceStack> ctx, final SuggestionsBuilder builder) {
builder.suggest(1);
builder.suggest(16);
builder.suggest(32);
builder.suggest(64);
return builder.buildFuture();
}
private static int executeCommandLogic(final CommandContext<CommandSourceStack> ctx) {
if (!(ctx.getSource().getExecutor() instanceof Player player)) {
return Command.SINGLE_SUCCESS;
}
final int firstEmptySlot = player.getInventory().firstEmpty();
if (firstEmptySlot == -1) {
player.sendRichMessage("<light_purple>You do not have enough space in your inventory!");
return Command.SINGLE_SUCCESS;
}
final ItemStack item = ctx.getArgument("item", ItemStack.class);
final int amount = IntegerArgumentType.getInteger(ctx, "amount");
item.setAmount(amount);
player.getInventory().setItem(firstEmptySlot, item);
player.sendRichMessage("<light_purple>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;
}
}
Example: Filtering by User Input:
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();
}
Custom Arguments
Custom arguments are wrappers around existing argument types, allowing developers to provide suggestions and reusable parsing logic.
Why Use Custom Arguments?
Example: An argument for an online operator player. Without custom arguments, it requires significant logic in the executes(...) method and duplication if used in multiple command nodes.
Example without Custom Arguments:
Commands.argument("player", ArgumentTypes.player())
.suggests((ctx, builder) -> {
Bukkit.getOnlinePlayers().stream()
.filter(ServerOperator::isOp)
.map(Player::getName)
.filter(name -> name.toLowerCase(Locale.ROOT).startsWith(builder.getRemainingLowerCase()))
.forEach(builder::suggest);
return builder.buildFuture();
})
.executes(ctx -> {
final Player player = ctx.getArgument("player", PlayerSelectorArgumentResolver.class).resolve(ctx.getSource()).getFirst();
if (!player.isOp()) {
final Message message = MessageComponentSerializer.message().serialize(text(player.getName() + " is not a server operator!"));
throw new SimpleCommandExceptionType(message).create();
}
ctx.getSource().getSender().sendRichMessage("Player <player> is an operator!", Placeholder.component("player", player.displayName()));
return Command.SINGLE_SUCCESS;
});
Example with Custom Arguments:
OppedPlayerArgument.java
@NullMarked
public final class OppedPlayerArgument implements CustomArgumentType<Player, PlayerSelectorArgumentResolver> {
private static final SimpleCommandExceptionType ERROR_BAD_SOURCE = new SimpleCommandExceptionType(MessageComponentSerializer.message().serialize(Component.text("The source needs to be a CommandSourceStack!")));
private static final DynamicCommandExceptionType ERROR_NOT_OPERATOR = new DynamicCommandExceptionType(name -> {
return MessageComponentSerializer.message().serialize(Component.text(name + " is not a server operator!"));
});
@Override
public Player parse(StringReader reader) {
throw new UnsupportedOperationException("This method will never be called.");
}
@Override
public <S> Player parse(StringReader reader, S source) throws CommandSyntaxException {
if (!(source instanceof CommandSourceStack stack)) {
throw ERROR_BAD_SOURCE.create();
}
final Player player = getNativeType().parse(reader).resolve(stack).getFirst();
if (!player.isOp()) {
throw ERROR_NOT_OPERATOR.create(player.getName());
}
return player;
}
@Override
public ArgumentType<PlayerSelectorArgumentResolver> getNativeType() {
return ArgumentTypes.player();
}
@Override
public <S> CompletableFuture<Suggestions> listSuggestions(CommandContext<S> ctx, SuggestionsBuilder builder) {
Bukkit.getOnlinePlayers().stream()
.filter(ServerOperator::isOp)
.map(Player::getName)
.filter(name -> name.toLowerCase(Locale.ROOT).startsWith(builder.getRemainingLowerCase()))
.forEach(builder::suggest);
return builder.buildFuture();
}
}
Command Declaration:
Commands.argument("player", new OppedPlayerArgument())
.executes(ctx -> {
final Player player = ctx.getArgument("player", Player.class);
ctx.getSource().getSender().sendRichMessage("Player <player> is an operator!", Placeholder.component("player", player.displayName()));
return Command.SINGLE_SUCCESS;
});
This is more readable, easier to understand, and reusable.
Examining the CustomArgumentType Interface:
package io.papermc.paper.command.brigadier.argument;
@NullMarked
public interface CustomArgumentType<T, N> extends ArgumentType<T> {
@Override
T parse(final StringReader reader) throws CommandSyntaxException;
@Override
default <S> T parse(final StringReader reader, final S source) throws CommandSyntaxException {
return ArgumentType.super.parse(reader, source);
}
ArgumentType<N> getNativeType();
@Override
@ApiStatus.NonExtendable
default Collection<String> getExamples() {
return this.getNativeType().getExamples();
}
@Override
default <S> CompletableFuture<Suggestions> listSuggestions(final CommandContext<S> context, final SuggestionsBuilder builder) {
return ArgumentType.super.listSuggestions(context, builder);
}
}
Generic Types:
T: The type returned whenCommandContext#getArgumentis called.N: The native type of the underlying argument.S: The command source type (usuallyCommandSourceStack).
Methods:
| Method Declaration | Description |
|---|---|
ArgumentType<N> getNativeType() |
Declares the underlying argument type used as a base for client-side validation. |
T parse(final StringReader reader) throws CommandSyntaxException |
Used if T parse(StringReader, S) is not overridden. Runs conversion and validation logic. |
default <S> T parse(final StringReader reader, final S source) throws CommandSyntaxException |
If overridden, this method is preferred over T parse(StringReader). Includes the source in parsing logic. |
default Collection<String> getExamples() |
Should not be overridden. Used internally for argument type differentiation. |
default <S> CompletableFuture<Suggestions> listSuggestions(final CommandContext<S> context, final SuggestionsBuilder builder) |
Equivalent to RequiredArgumentBuilder#suggests(SuggestionProvider<S>). Override to send custom suggestions. |
A Very Basic Implementation:
package io.papermc.commands;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import io.papermc.paper.command.brigadier.argument.CustomArgumentType;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class BasicImplementation implements CustomArgumentType<String, String> {
@Override
public String parse(StringReader reader) {
return reader.readUnquotedString();
}
@Override
public ArgumentType<String> getNativeType() {
return StringArgumentType.word();
}
}
CustomArgumentType.Converted<T, N>:
Used when parsing the native type to a new type. Extends CustomArgumentType and adds two methods:
T convert(N nativeType) throws CommandSyntaxException;default <S> T convert(final N nativeType, final S source) throws CommandSyntaxException { return this.convert(nativeType); }
These methods provide the parsed native type instead of a StringReader, reducing the need for manual string reader operations.
Error Handling During the Suggestions Phase:
Showing invalid input in red is not possible with custom arguments because the client can only validate known arguments, and a CommandSyntaxException cannot be thrown during the suggestions phase. Literals can achieve this, but they cannot be dynamically modified during server runtime.
Example: Ice-cream Argument:
// IceCreamFlavor.java
package io.papermc.commands.icecream;
import org.jspecify.annotations.NullMarked;
@NullMarked
public enum IceCreamFlavor {
VANILLA,
CHOCOLATE,
STRAWBERRY;
@Override
public String toString() {
return name().toLowerCase();
}
}
// IceCreamArgument.java
package io.papermc.commands.icecream;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.DynamicCommandExceptionType;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import io.papermc.paper.command.brigadier.argument.CustomArgumentType;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.jspecify.annotations.NullMarked;
import java.util.Locale;
import java.util.concurrent.CompletableFuture;
@NullMarked
public class IceCreamArgument implements CustomArgumentType.Converted<IceCreamFlavor, String> {
private static final DynamicCommandExceptionType ERROR_INVALID_FLAVOR = new DynamicCommandExceptionType(flavor -> {
return LegacyComponentSerializer.legacySection().serialize(Component.text(flavor + " is not a valid flavor!"));
});
@Override
public IceCreamFlavor convert(String nativeType) throws CommandSyntaxException {
try {
return IceCreamFlavor.valueOf(nativeType.toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException ignored) {
throw ERROR_INVALID_FLAVOR.create(nativeType);
}
}
@Override
public <S> CompletableFuture<Suggestions> listSuggestions(CommandContext<S> context, SuggestionsBuilder builder) {
for (IceCreamFlavor flavor : IceCreamFlavor.values()) {
String name = flavor.toString();
if (name.startsWith(builder.getRemainingLowerCase())) {
builder.suggest(flavor.toString());
}
}
return builder.buildFuture();
}
@Override
public ArgumentType<String> getNativeType() {
return StringArgumentType.word();
}
@Override
public IceCreamFlavor parse(StringReader reader) throws CommandSyntaxException {
return null;
}
}
Command Declaration:
Commands.literal("icecream")
.then(Commands.argument("flavor", new IceCreamArgument())
.executes(ctx -> {
final IceCreamFlavor flavor = ctx.getArgument("flavor", IceCreamFlavor.class);
ctx.getSource().getSender().sendRichMessage("<b><red>Y<green>U<aqua>M<light_purple>!</b> You just had a scoop of <flavor>!",
Placeholder.unparsed("flavor", flavor.toString()));
return Command.SINGLE_SUCCESS;
}))
.build();
Minecraft-specific
(This section's content is not available in the provided documents.)
# API
## Introduction
This knowledge base provides information on various APIs available in Paper and Folia.
## Component API
### Introduction
Components offer a structured way to represent text in Minecraft, providing advantages over plain text strings. Paper and Velocity implement the Adventure API for enhanced component support.
### Why you should use Components
* Components are tree-like structures, inheriting styles and colors.
* Components can translate text based on client language or show client-specific keybinds.
* Components support RGB colors and interaction events.
* Mojang plans to remove client support for the legacy `§` format.
### Usage
* Components are used for item names, lore, bossbars, team prefixes/suffixes, and custom names.
* Deprecated methods dealing with the legacy format indicate better component alternatives.
### Creating components
* Components are interacted with as objects.
* Use builders to construct complex components efficiently.
* Example:
```java
// This is a sub-optimal construction of the
// component as each change creates a new component
final Component component = Component.text("Hello").color(TextColor.color(0x13f832)).append(Component.text("world!", NamedTextColor.GREEN));
/* This is an optimal use of the builder to create
the same component. Also note that Adventure
Components are designed for use with static method imports
to make code less verbose */
final Component component = text().content("Hello").color(color(0x13f832)).append(text("world!", GREEN)).build();
```
* Refer to the Adventure documentation for in-depth information.
### MiniMessage
* MiniMessage is a string representation of components.
* It is superior to the legacy string format, supporting style inheritance and complex component types.
* Example:
```java
final Component component = MiniMessage.miniMessage().deserialize("<#438df2><b>This is the parent component; its style is applied to all children.\n<u><!b>This is the first child, which is rendered after the parent</!b></u><key:key.inventory></b></#438df2>");
// if the syntax above is too verbose for you, create a helper method!
public final class Components {
public static Component mm(String miniMessageString) {
// mm, short for MiniMessage
return MiniMessage.miniMessage().deserialize(miniMessageString);
}
}
```
### JSON format
(No information provided in the document)
### Serializers
(No information provided in the document)
#### GsonComponentSerializer
(No information provided in the document)
#### MiniMessage
(No information provided in the document)
#### PlainTextComponentSerializer
(No information provided in the document)
#### LegacyComponentSerializer
(No information provided in the document)
### Internationalization
It is generally a good idea to support translations in your plugin to appeal to a larger user base. Adventure simplifies this by adding a server-side translation layer.
* **GlobalTranslator**: All translation is done through `GlobalTranslator`. You can render translations yourself and add new sources by creating instances of `TranslationStore` or implementing the `Translator` interface.
* **Where Translations Work**: Server-side translations work anywhere the component API exists, except for `ItemStack` display text. This includes chat, entity display names, scoreboards, and tab lists.
* **ResourceBundle Example**:
```java
src/main/resources/your/plugin/Bundle_en_US.properties
some.translation.key=Translated Message: {0}
TranslationStore.StringBased<MessageFormat> store = TranslationStore.messageFormat(Key.key("namespace:value"));
ResourceBundle bundle = ResourceBundle.getBundle("your.plugin.Bundle", Locale.US, UTF8ResourceBundleControl.get());
store.registerAll(Locale.US, bundle, true);
GlobalTranslator.translator().addSource(store);
Component.translatable("some.translation.key", Component.text("The Argument"))
```
This will show to clients using the US English language:
```text
Translated Message: The Argument
```
### Audiences
Audiences wrap a collection of recipients that can receive messages. They are used to send messages to individual players, groups of players, or the entire server.
* **Who is an Audience?**
* All `CommandSender`s are single audiences (players, console, command blocks).
* `Server`, `Team`, and `World` are forwarding audiences (made up of multiple audiences).
* All `Audience` methods are available on `CommandSender`, `Server`, `Team`, and `World`.
* **ForwardingAudience**: Wraps a collection of `Audience` instances and forwards messages.
```java
// Server is a ForwardingAudience which includes all online players and the console
ForwardingAudience audience = Bukkit.getServer();
// To construct an audience from a collection of players, use:
Audience audience = Audience.audience(Audience...);
// If you pass in a single Audience, it will be returned as-is. If you pass in a collection of Audiences, they will be
// wrapped in a ForwardingAudience.
```
* **What do Audiences do?**
* Interact with players by sending messages, playing sounds, and showing bossbars.
* Send other parts of the API to players (e.g., `Audience#sendMessage(Component)`).
* **Pointers**: Provide arbitrary information like display name or UUID.
```java
// Get the uuid from an audience member, returning an Optional<UUID>
Optional<UUID> uuid = audience.get(Identity.UUID);
// Get the display name, returning a default
Component name = audience.getOrDefault(Identity.DISPLAY_NAME, Component.text("no display name!"));
```
### Signed messages
(No information provided in the document)
## Event API
### Listeners
(No information provided in the document)
### Custom events
(No information provided in the document)
### Handler lists
(No information provided in the document)
### Chat events
(No information provided in the document)
## Entity API
### Teleportation
(No information provided in the document)
### Display entities
(No information provided in the document)
### Inventories
(No information provided in the document)
### Menu Type API
(No information provided in the document)
#### Experimental
(No information provided in the document)
### Custom InventoryHolders
(No information provided in the document)
## Lifecycle API
### Introduction
(No information provided in the document)
### Datapack discovery
(No information provided in the document)
#### Experimental
(No information provided in the document)
## Data components
#### Experimental
(No information provided in the document)
## Persistent data container (PDC)
(No information provided in the document)
## Scheduling
(No information provided in the document)
## Plugin messaging
(No information provided in the document)
## Plugin configuration
(No information provided in the document)
## Registries
#### Experimental
(No information provided in the document)
## Dialog API
#### Experimental
(No information provided in the document)
## Recipes
(No information provided in the document)
## Particles
(No information provided in the document)
## Supporting Paper and Folia
(No information provided in the document)
## Roadmap
(No information provided in the document)
## Miscellaneous
(No information provided in the document)
## Using databases
(No information provided in the document)
## Debugging your plugin
(No information provided in the document)
## Minecraft internals
(No information provided in the document)
### Reading stacktraces
(No information provided in the document)
## Contributing
(No information provided in the document)
## Events
(No information provided in the document)
## Command API
### Basics
#### Introduction
(No information provided in the document)
#### Command trees
(No information provided in the document)
#### Arguments and literals
(No information provided in the document)
#### Executors
(No information provided in the document)
#### Registration
##### Registering commands
Comparison of registering commands using the old Bukkit way and the new Paper way.
###### The old Bukkit way
* Define a class extending `BukkitCommand` and implement `execute(...)` and `tabComplete(...)` methods.
* Example:
```java
BukkitPartyCommand.java
package your.package.name;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.command.defaults.BukkitCommand;
import org.bukkit.entity.Player;
import org.jspecify.annotations.NullMarked;
import java.util.List;
@NullMarked
public class BukkitPartyCommand extends BukkitCommand {
public BukkitPartyCommand(String name, String description, String usageMessage, List<String> aliases) {
super(name, description, usageMessage, aliases);
}
@Override
public boolean execute(CommandSender sender, String commandLabel, String[] args) {
if (args.length == 0) {
sender.sendPlainMessage("Please provide a player!");
return false;
}
final Player targetPlayer = Bukkit.getPlayer(args[0]);
if (targetPlayer == null) {
sender.sendPlainMessage("Please provide a valid player!");
return false;
}
targetPlayer.sendPlainMessage(sender.getName() + " started partying with you!");
sender.sendPlainMessage("You are now partying with " + targetPlayer.getName() + "!");
return true;
}
@Override
public List<String> tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException {
if (args.length == 1) {
return Bukkit.getOnlinePlayers().stream().map(Player::getName).toList();
}
return List.of();
}
}
```
* Register the command:
```java
PluginClass.java
this.getServer().getCommandMap().register(this.getName().toLowerCase(), new BukkitPartyCommand("bukkitparty", "Have a party", "/bukkitparty <player>", List.of()));
```
###### The new Paper way
* Retrieve a `LiteralCommandNode<CommandSourceStack>` using `Commands.literal(final String literal)`.
* Define arguments and executors using `LiteralArgumentBuilder<CommandSourceStack>`.
* Build the `LiteralCommandNode` using `LiteralArgumentBuilder#build()`.
* Example:
```java
PaperPartyCommand.java
public static LiteralCommandNode<CommandSourceStack> createCommand(final String commandName) {
return Commands.literal(commandName)
.then(Commands.argument("target", ArgumentTypes.player()).executes(ctx -> {
final PlayerSelectorArgumentResolver playerSelector = ctx.getArgument("target", PlayerSelectorArgumentResolver.class);
final Player targetPlayer = playerSelector.resolve(ctx.getSource()).getFirst();
final CommandSender sender = ctx.getSource().getSender();
targetPlayer.sendPlainMessage(sender.getName() + " started partying with you!");
sender.sendPlainMessage("You are now partying with " + targetPlayer.getName() + "!");
return Command.SINGLE_SUCCESS;
}))
.build();
}
```
* Register the command using `LifecycleEventManager`:
```java
PluginClass.java
this.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, commands -> {
commands.registrar().register(PaperPartyCommand.createCommand("paperparty"), "Have a nice party");
});
```
#### Requirements
This section explains how to restrict commands using the `requires(Predicate<S>)` method of the `ArgumentBuilder<S>` class.
* **Defining permissions**:
* Check permissions on the `command sender`.
* Example:
```java
Commands.literal("testcmd")
.requires(sender -> sender.getSender().hasPermission("permission.test"))
.executes(ctx -> {
ctx.getSource().getSender().sendRichMessage("<gold>You have permission to run this command!</gold>");
return Command.SINGLE_SUCCESS;
});
```
* Require a sender to be a server operator:
```java
Commands.literal("testcmd")
.requires(sender -> sender.getSender().isOp())
.executes(ctx -> {
ctx.getSource().getSender().sendRichMessage("<gold>You are a server operator!</gold>");
return Command.SINGLE_SUCCESS;
});
```
* **Defining more advanced predicates**:
* Any boolean can be returned in the predicate.
* Example (checking for a diamond sword):
```java
Commands.literal("givesword")
.requires(sender -> sender.getExecutor() instanceof Player player && !player.getInventory().contains(Material.DIAMOND_SWORD))
.executes(ctx -> {
if (ctx.getSource().getExecutor() instanceof Player player) {
player.getInventory().addItem(ItemType.DIAMOND_SWORD.createItemStack());
}
return Command.SINGLE_SUCCESS;
});
```
* Client-side command visibility issue: The client might show the command as executable even if the requirement is not met.
* Solution: Use `Player#updateCommands()` to resend commands to the client.
```java
Commands.literal("reloadcommands")
.executes(ctx -> {
if (ctx.getSource().getExecutor() instanceof Player player) {
player.updateCommands();
player.sendRichMessage("<gold>Successfully updated your commands!</gold>");
}
return Command.SINGLE_SUCCESS;
});
```
* **Automating command reloads**:
* Automate command reloads instead of forcing players to reload manually.
* `updateCommands()` is thread-safe but should be used sparingly to avoid excessive bandwidth usage.
* **Restricted commands**:
* From 1.21.6 onwards, commands can be restricted, requiring confirmation from the player.
* Vanilla commands requiring operator status are restricted by default.
* **Restricting your commands**:
* Wrap the predicate with `Commands.restricted(...)`.
* Example:
```java
Commands.literal("test-req")
.requires(Commands.restricted(source -> true))
.executes(ctx -> {
ctx.getSource().getSender().sendRichMessage("You passed!");
return Command.SINGLE_SUCCESS;
});
```
* Complex example:
```java
Commands.literal("mycommand")
.requires(Commands.restricted(source -> source.getSender().hasPermission("my.custom.permission") && source.getExecutor() instanceof Player player && player.getGameMode() == GameMode.ADVENTURE))
.executes(ctx -> {
// Command logic
});
```
#### Suggestions
(No information provided in the document)
#### Custom arguments
(No information provided in the document)
##### Arguments
(No information provided in the document)
##### Minecraft-specific
(No information provided in the document)
###### Location
(No information provided in the document)
###### Entities and players
(No information provided in the document)
###### Registry
(No information provided in the document)
##### Paper-specific
(No information provided in the document)
###### Enums
(No information provided in the document)
###### Predicates
(No information provided in the document)
### Adventure
(No information provided in the document)
### Miscellaneous
#### Basic commands
Paper provides the `BasicCommand` interface for simple commands.
* Implement the `BasicCommand` interface.
* Override the `execute(CommandSourceStack source, String[] args)` method.
* Optional methods:
* `Collection<String> suggest(CommandSourceStack source, String[] args)`: Tab completion suggestions.
* `boolean canUse(CommandSender sender)`: Basic Brigadier `requires` structure.
* `@Nullable String permission()`: Sets the required permission.
* **Simple Usage Example**:
```java
YourCommand.java
package your.package.name;
import io.papermc.paper.command.brigadier.BasicCommand;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class YourCommand implements BasicCommand {
@Override
public void execute(CommandSourceStack source, String[] args) {
}
}
```
* `CommandSourceStack` provides information about the sender, location, and executor.
* **Optional Methods**:
* `suggest(CommandSourceStack, String[])`: Provides tab completion suggestions, similar to `TabCompleter`.
* `canUse(CommandSender)`: Defines a basic Brigadier `requires` structure. Overriding this negates the effect of overriding `permission()`.
* `permission()`: Sets the required permission.
```java
BasicCommand.java
default boolean canUse(final CommandSender sender) {
final String permission = this.permission();
return permission == null || sender.hasPermission(permission);
}
```
* **Registering Basic Commands**:
```java
YourPlugin.java
public class YourPlugin extends JavaPlugin {
@Override
public void onEnable() {
BasicCommand yourCommand = ...;
registerCommand("mycommand", yourCommand);
}
}
```
* **Functional Interfaces**: You can use lambda expressions for simple commands, but it is not recommended for readability.
```java
@Override
public void onEnable() {
registerCommand("quickcmd", (source, args) -> source.getSender().sendRichMessage("<yellow>Hello!</yellow>"));
}
```
* **Example: Broadcast Command**:
```java
BroadcastCommand.java
package your.package.name;
import io.papermc.paper.command.brigadier.BasicCommand;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.bukkit.Bukkit;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public class BroadcastCommand implements BasicCommand {
@Override
public void execute(CommandSourceStack source, String[] args) {
final Component name = source.getExecutor() != null ? source.getExecutor().name() : source.getSender().name();
if (args.length == 0) {
source.getSender().sendRichMessage("<red>You cannot send an empty broadcast!</red>");
return;
}
final String message = String.join(" ", args);
final Component broadcastMessage = MiniMessage.miniMessage().deserialize("<red><bold>BROADCAST</red> <name> <dark_gray>»</dark_gray> <message>",
Placeholder.component("name", name),
Placeholder.unparsed("message", message));
Bukkit.broadcast(broadcastMessage);
}
@Override
@Nullable
public String permission() {
return "example.broadcast.use";
}
}
```
Register the command:
```java
PluginMainClass.java
@Override
public void onEnable() {
registerCommand("broadcast", new BroadcastCommand());
}
```
* **Adding Suggestions**: Suggest player names.
```java
@Override
public Collection<String> suggest(CommandSourceStack source, String[] args) {
return Bukkit.getOnlinePlayers().stream().map(Player::getName).toList();
}
```
Filter suggestions based on input:
```java
@Override
public Collection<String> suggest(CommandSourceStack source, String[] args) {
if (args.length == 0) {
return Bukkit.getOnlinePlayers().stream().map(Player::getName).toList();
}
return Bukkit.getOnlinePlayers().stream().map(Player::getName).filter(name -> name.toLowerCase().startsWith(args[args.length - 1].toLowerCase())).toList();
}
```
* **Final Code Example**:
```java
BroadcastCommand.java
package your.package.name;
import io.papermc.paper.command.brigadier.BasicCommand;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.util.Collection;
@NullMarked
public class BroadcastCommand implements BasicCommand {
@Override
public void execute(CommandSourceStack source, String[] args) {
final Component name = source.getExecutor() != null ? source.getExecutor().name() : source.getSender().name();
if (args.length == 0) {
source.getSender().sendRichMessage("<red>You cannot send an empty broadcast!</red>");
return;
}
final String message = String.join(" ", args);
final Component broadcastMessage = MiniMessage.miniMessage().deserialize("<red><bold>BROADCAST</red> <name> <dark_gray>»</dark_gray> <message>",
Placeholder.component("name", name),
Placeholder.unparsed("message", message));
Bukkit.broadcast(broadcastMessage);
}
@Override
@Nullable
public String permission() {
return "example.broadcast.use";
}
@Override
public Collection<String> suggest(CommandSourceStack source, String[] args) {
if (args.length == 0) {
return Bukkit.getOnlinePlayers().stream().map(Player::getName).toList();
}
return Bukkit.getOnlinePlayers().stream().map(Player::getName).filter(name -> name.toLowerCase().startsWith(args[args.length - 1].toLowerCase())).toList();
}
}
```
#### Comparison
(No information provided in the document)
# Component API
This documentation page applies to both the Paper and Velocity projects. Since Minecraft 1.7, the game has utilized components to represent text to be displayed by clients. Paper and Velocity natively implements the Adventure API to add component support wherever possible.
## Why You Should Use Components
* Previously, text was a linear structure with confusing symbols like `§c` and `§k` to control basic colors and styles.
* Components are a tree-like structure that inherits style and colors from their parents.
* Components have several types which do different things than just display raw text, like translating text to the client’s language based on a key, or showing a client-specific keybind to a player.
* All these component types support more style options like any RGB color, interaction events (click and hover). The other component types and these style options have poor or missing representations in the legacy string format.
## Usage
Representing text as components is now the supported way of representing text for Paper and Velocity. They are used for almost all aspects of text being displayed to clients. Text like item names, lore, bossbars, team prefixes and suffixes, custom names, and much more all support components in respective APIs. Mojang has stated that client support for the legacy format with `§` will be removed in the future.
**Tip:** In the Paper API, there are lots of deprecated methods and types that deal with this legacy format. This is to signal that a better alternative in components is available and should be migrated to going forward.
## Creating Components
Components can be interacted with as objects. There are different interfaces for each type along with builders for all the types. These objects are immutable so when constructing more complex components, it’s recommended to use builders to avoid creating new Component instances with every change.
```java
// This is a sub-optimal construction of the
// component as each change creates a new component
final Component component = Component.text("Hello")
.color(TextColor.color(0x13f832))
.append(Component.text("world!", NamedTextColor.GREEN));
/* This is an optimal use of the builder to create
the same component. Also note that Adventure
Components are designed for use with static method imports
to make code less verbose */
final Component component = text()
.content("Hello")
.color(color(0x13f832))
.append(text("world!", GREEN))
.build();
In-Depth Documentation: For complete documentation on the Adventure Component API Paper and Velocity use, please look at the Adventure documentation.
MiniMessage
Paper and Velocity include the MiniMessage library, which is a string representation of components. If you prefer working with strings rather than objects, MiniMessage is vastly superior to the legacy string format. It can utilize the tree structure for style inheritance and can represent the more complex component types while legacy cannot.
final Component component = MiniMessage.miniMessage().deserialize(
"<#438df2><b>This is the parent component; its style is " +
"applied to all children.\n<u><!b>This is the first child, " +
"which is rendered after the parent</!b></u><key:key.inventory></b></#438df2>"
);
// if the syntax above is too verbose for you, create a helper method!
public final class Components {
public static Component mm(String miniMessageString) {
// mm, short for MiniMessage
return MiniMessage.miniMessage().deserialize(miniMessageString);
}
}
import static io.papermc.docs.util.Components.mm;
// replace with your own package
final Component component = mm("<blue>Hello <red>World!");
We recommend using this format for user-facing input such as commands or configuration values.
In-Depth Documentation: MiniMessage is a part of Adventure, and you can find its documentation on Adventure’s documentation.
Tip: MiniMessage has a web viewer, which is useful for constructing more complicated components and seeing the results in real time.
JSON Format
Components can be serialized and deserialized from a standard JSON format. This format is used in Vanilla in various commands which accept component arguments like /tellraw. Below is a simple example of this format.
{
"text": "This is the parent component; its style is applied to all children.\n",
"color": "#438df2",
"bold": true,
"extra": [
{
"text": "This is this first child, which is rendered after the parent",
"underlined": true,
// This overrides the parent's "bold" value just for this component
"bold": false
},
{
// This is a keybind component which will display the client's keybind for that action
"keybind": "key.inventory"
}
]
}
In-Depth Documentation: The JSON format is fully documented on the Minecraft Wiki.
Tip: There are online tools to make generating this format much easier like JSON Text Generator.
Serializers
Paper and Velocity come bundled with different serializers for converting between Components and other forms of serialized text.
GsonComponentSerializer
Converts between Component and JSON-formatted strings with convenience methods to directly deal with Gson’s JsonElement. This conversion is lossless and is the preferred form of serialization for components that do not have to be edited by users regularly.
MiniMessage
Converts between Component and a MiniMessage-formatted string. This conversion is lossless and is the preferred form of serialization for components that have to be edited by users. There is also extensive customization you can add to the serializer, which is documented here.
PlainTextComponentSerializer
Serializes a Component into a plain text string. This is very lossy as all style information as well as most other types of components will lose information. There may be special handling for TranslatableComponents to be serialized into a default language, but generally this shouldn’t be used except in certain circumstances, like logging to a text file.
LegacyComponentSerializer
Caution: This is not recommended for use as the legacy format may be removed in the future.
Converts between Component and the legacy string format. This conversion is very lossy as component types and events do not have a legacy string representation. A more useful use case is converting legacy text to MiniMessage format in a migration process.
final String legacyString = ChatColor.RED + "This is a legacy" + ChatColor.GOLD + "string";
// runs the legacy string through two serializers to convert legacy -> MiniMessage
final String miniMessageString = MiniMessage.miniMessage().serialize(LegacyComponentSerializer.legacySection().deserialize(legacyString));
Note: There are 2 built-in legacy serializers, one dealing with § symbols and the other for & symbols. They have their own instances available through LegacyComponentSerializer#legacySection() and LegacyComponentSerializer#legacyAmpersand().
Signed Messages
Since Minecraft version 1.19, the client now signs any messages it sends so that they are uniquely identifiable and verifiable to be sent by a specific player. With this update, they also introduced the ability delete specific messages previously sent by a player.
Note: This guide does not go in-depth into the specifics of chat signing and its implementation on either client or server. For a full overview, you can refer to this linked gist.
How are signed messages represented in code?
Paper uses Adventure’s SignedMessage object to represent a signed message. We differentiate two kinds of signed messages: system messages and non-system messages.
- System messages (checked with
SignedMessage#isSystem()) are messages send by the server. - Non-system messages are not.
You can also differentiate the signed plain text String content of the message (SignedMessage#message()) from the unsigned, nullable Component content (SignedMessage#unsignedContent()).
Obtaining a signed message
Signed messages can be obtained in two ways:
- From an
AsyncChatEventusingAbstractChatEvent#signedMessage(). - From an
ArgumentTypes.signedMessage()Brigadier argument type.
Using signed messages
You can send signed message objects to an Audience using the Audience#sendMessage(SignedMessage, ChatType.Bound) method. You can obtain a ChatType.Bound object from the ChatType interface.
Deleting messages is much simpler. Adventure provides the Audience#deleteMessage(SignedMessage) or Audience#deleteMessage(SignedMessage.Signature) methods for that.
Example: Making user sent messages deletable
For our example, we will create a chat format plugin which allows a user to delete their own messages in case they made a mistake. For this we will use the AsyncChatEvent.
AsyncChatEvent
The AsyncChatEvent is covered in the chat events documentation page. If you want to read up on more detail on the chat renderer, you can do so there.
In-game preview
(No preview provided in the original document)
Code
SignedChatListener.java
package io.papermc.docs.signedmessages;
import io.papermc.paper.event.player.AsyncChatEvent;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
public class SignedChatListener implements Listener {
@EventHandler
void onPlayerChat(AsyncChatEvent event) {
// We modify the chat format, so we use a chat renderer.
event.renderer((player, playerName, message, viewer) -> {
// This is the base format of our message. It will format chat as "<player> » <message>".
final Component base = Component.textOfChildren(
playerName.colorIfAbsent(NamedTextColor.GOLD),
Component.text(" » ", NamedTextColor.DARK_GRAY),
message
);
// Send the base format to any player who is not the sender.
if (viewer != player) {
return base;
}
// Create a base delete suffix. The creation is separated into two
// parts purely for readability reasons.
final Component deleteCrossBase = Component.textOfChildren(
Component.text("[", NamedTextColor.DARK_GRAY),
Component.text("X", NamedTextColor.DARK_RED, TextDecoration.BOLD),
Component.text("]", NamedTextColor.DARK_GRAY)
);
// Add a hover and click event to the delete suffix.
final Component deleteCross = deleteCrossBase
.hoverEvent(Component.text("Click to delete your message!", NamedTextColor.RED))
// We retrieve the signed message with event.signedMessage() and request a server-wide deletion if the
// deletion cross were to be clicked.
.clickEvent(ClickEvent.callback(audience -> Bukkit.getServer().deleteMessage(event.signedMessage())));
// Send the base format but with the delete suffix.
return base.appendSpace().append(deleteCross);
});
}
}
Custom Events
Creating custom events is a great way to add functionality to your plugin. This will allow other plugins to listen to your custom events and add functionality to your plugin.
Creating a custom event
To create a custom event, you need to create a class that extends Event. Each event requires a HandlerList that will contain all the listeners that are listening to that event. The only exception to this requirement is when you have an event class that cannot be fired, but serves as a parent for other events instead. An example of this is BlockPistonEvent, which cannot be listened to directly. This list is used to call the listeners when the event is called.
Note: Although getHandlerList is not inherited from Event, you need to add a static getHandlerList() method and return the HandlerList for your event. Both methods are required for your event to work.
PaperIsCoolEvent.java
public class PaperIsCoolEvent extends Event {
private static final HandlerList HANDLER_LIST = new HandlerList();
public static HandlerList getHandlerList() {
return HANDLER_LIST;
}
@Override
public HandlerList getHandlers() {
return HANDLER_LIST;
}
}
Now that we have created our event, we can add some functionality to it. Perhaps this will contain a message that will be broadcast to the server when the event is called.
PaperIsCoolEvent.java
public class PaperIsCoolEvent extends Event {
private static final HandlerList HANDLER_LIST = new HandlerList();
private Component message;
public PaperIsCoolEvent(Component message) {
this.message = message;
}
public Component getMessage() {
return this.message;
}
public void setMessage(Component message) {
this.message = message;
}
public static HandlerList getHandlerList() {
return HANDLER_LIST;
}
@Override
public HandlerList getHandlers() {
return HANDLER_LIST;
}
}
Calling the event
Now that we have created our event, we can call it.
ExamplePlugin.java
public class ExamplePlugin extends JavaPlugin {
// ...
public void callCoolPaperEvent() {
PaperIsCoolEvent coolEvent = new PaperIsCoolEvent(Component.text("Paper is cool!"));
coolEvent.callEvent();
// Plugins could have changed the message from inside their listeners here. So we need to get the message again.
// This event structure allows for other plugins to change the message to their taste.
// Like, for example, a plugin that adds a prefix to all messages.
Bukkit.broadcast(coolEvent.getMessage());
}
}
Implementing cancellation
If you want to allow your event to be cancelled, you can implement the Cancellable interface.
PaperIsCoolEvent.java
public class PaperIsCoolEvent extends Event implements Cancellable {
private static final HandlerList HANDLER_LIST = new HandlerList();
private Component message;
private boolean cancelled;
// ...
@Override
public boolean isCancelled() {
return this.cancelled;
}
@Override
public void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
}
}
Now, when the event is called, you can check if it is cancelled and act accordingly.
ExamplePlugin.java
public class ExamplePlugin extends JavaPlugin {
// ...
public void callCoolPaperEvent() {
PaperIsCoolEvent coolEvent = new PaperIsCoolEvent(Component.text("Paper is cool!"));
coolEvent.callEvent();
if (!coolEvent.isCancelled()) {
Bukkit.broadcast(coolEvent.getMessage());
}
}
}
When an event is cancellable, Event#callEvent() will return false if the event was cancelled. This allows you to directly use callEvent in your if statement, instead of having to check Cancellable#isCancelled() manually.
ExamplePlugin.java
public class ExamplePlugin extends JavaPlugin {
// ...
public void callCoolPaperEvent() {
PaperIsCoolEvent coolEvent = new PaperIsCoolEvent(Component.text("Paper is cool!"));
if (coolEvent.callEvent()) {
// Directly get the output from callEvent
Bukkit.broadcast(coolEvent.getMessage());
}
}
}
Custom InventoryHolders
InventoryHolders are a way to identify your plugin’s inventories in events.
Why use an InventoryHolder?
InventoryHolders simplify the steps you need to do to make sure an inventory was created by your plugin. Using inventory names for identification is unreliable, as other plugins, or even players, can create inventories with names the exact same as yours. With components, you also need to make sure the name is exactly the same or serialize it to other formats. Custom InventoryHolders have no such downsides and by using them you’re guaranteed to have methods available to handle your inventory.
Creating a custom holder
The first step is to implement the InventoryHolder interface. We can do this the following way: create a new class that will create our Inventory in the constructor.
Note: The constructor takes your main plugin class as an argument in order to create the Inventory. If you wish, you can use the static method Bukkit#createInventory(InventoryHolder, int) instead and remove the argument.
MyInventory.java
public class MyInventory implements InventoryHolder {
private final Inventory inventory;
public MyInventory(MyPlugin plugin) {
// Create an Inventory with 9 slots, `this` here is our InventoryHolder.
this.inventory = plugin.getServer().createInventory(this, 9);
}
@Override
public Inventory getInventory() {
return this.inventory;
}
}
Opening the inventory
To open the inventory, first we have to instantiate our MyInventory class and then open the inventory for the player. You can do that wherever you need.
Note: We pass an instance of our plugin’s main class as it’s required by the constructor. If you’ve used the static method and removed the constructor argument you don’t have to pass it here.
Player player;
// Assume we have a Player instance.
// This can be a command, another event or anywhere else you have a Player.
MyInventory myInventory = new MyInventory(myPlugin);
player.openInventory(myInventory.getInventory());
Listening to an event
Once we have the inventory open, we can listen to any inventory events we like and check if Inventory#getHolder() returns an instance of our MyInventory.
@EventHandler
public void onInventoryClick(InventoryClickEvent event) {
Inventory inventory = event.getInventory();
// Check if the holder is our MyInventory,
// if yes, use instanceof pattern matching to store it in a variable immediately.
if (!(inventory.getHolder(false) instanceof MyInventory myInventory)) {
// It's not our inventory, ignore it.
return;
}
// Do what we need in the event.
}
Storing data on the holder
You can store extra data for your inventories on the InventoryHolder by adding fields and methods to your class. Let’s make an inventory that counts the amount of times we clicked a stone inside it.
First, let’s modify our MyInventory class a little:
MyInventory.java
public class MyInventory implements InventoryHolder {
private final Inventory inventory;
private int clicks = 0; // Store the amount of clicks.
public MyInventory(MyPlugin plugin) {
this.inventory = plugin.getServer().createInventory(this, 9);
// Set the stone that we're going to be clicking.
this.inventory.setItem(0, ItemStack.of(Material.STONE));
}
// A method we will call in the listener whenever the player clicks the stone.
public void addClick() {
this.clicks++;
this.updateCounter();
}
// A method that will update the counter item.
private void updateCounter() {
this.inventory.setItem(8, ItemStack.of(Material.BEDROCK, this.clicks));
}
@Override
public Inventory getInventory() {
return this.inventory;
}
}
Now, we can modify our listener to check if the player clicked the stone, and if so, add a click.
@EventHandler
public void onInventoryClick(InventoryClickEvent event) {
// We're getting the clicked inventory to avoid situations where the player
// already has a stone in their inventory and clicks that one.
Inventory inventory = event.getClickedInventory();
// Add a null check in case the player clicked outside the window.
if (inventory == null || !(inventory.getHolder(false) instanceof MyInventory myInventory)) {
return;
}
event.setCancelled(true);
ItemStack clicked = event.getCurrentItem();
// Check if the player clicked the stone.
if (clicked != null && clicked.getType() == Material.STONE) {
// Use the method we have on MyInventory to increment the field
// and update the counter.
myInventory.addClick();
}
}
Note: You can store the created MyInventory instance, e.g. on a Map<UUID, MyInventory> for per-player use, or as a field to share the inventory between all players, and use it to persist the counter even when opening the inventory for the next time.
Data Components
(Experimental) The data component API is currently experimental, and is additionally subject to change across versions.
The data component API provides a version-specific interface for accessing and manipulating item data that is otherwise not representable by the ItemMeta API. Through this API, you can read and modify properties of an item, so called data components, in a stable and object-oriented manner.
Introduction
What is a data component?
A data component represents a piece of data associated with an item. Vanilla items can have properties such as custom model data, container loot contents, banner patterns, or potion effects.
Structure
For implementation details, click here.
The prototype (default values)
Items come with an initial set of components that we call the prototype. These components are defined on the ItemType of the ItemStack. They control the base behavior of the item, representing a brand new item without any modifications.
The prototype gives items their initial properties such as if they are food, a tool, a weapon, etc.
The patch
The patch represents the modifications made to the item. This may include giving it a custom name, modifying the enchantments, damaging it, or adding to the lore. The patch is applied on top of the prototype, allowing us to make modifications to an item.
The patch also allows for removing components that were previously in the prototype. This is shown by the minecraft:tool example in red. We are removing this component, so this sword item will no longer break cobweb or other sword blocks faster.
We can also add new components, as seen from the new minecraft:enchantment_glint_override component, which allows us to make it appear as if it were enchanted.
Differences compared to ItemMeta
The ItemMeta API provides methods to modify ItemStacks in a hierarchical manner, such as CompassMeta, which allows you to modify the components of a minecraft:compass.
While ItemMeta is still very useful, it does not properly represent the prototype/patch relationship that Minecraft items use.
Key differences
Expanded data model
The data component API exposes a much broader and more detailed set of item properties than ItemMeta. Data components allow the entire item to be modified in a fashion that better represents how Minecraft does item modifications.
Version-specific
The data component API is designed to adapt to version changes. The data component API may experience breaking changes on version updates as Minecraft makes changes to components. Backwards compatibility is not promised.
Because ItemMeta is represented in a different format, breaking changes made to components by Mojang may not result in breaking changes to ItemMeta.
Builders and immutability
Many complex data components require a builder approach for construction and editing. All data types that are returned by the api are also immutable, so they will not directly modify the component.
Patch-only
ItemMeta only represents the patch of an ItemStack. This means that you cannot get the original properties (prototype) of the ItemStack, such as its default durability or default attributes.
No snapshots
Currently, ItemMeta represents a snapshot of an ItemStack’s patched map. This is expensive as it requires the entire patch to be read, even values that you may not be using.
The data component API integrates directly with ItemStack. Although conceptually similar, the data component API focuses on explicit, strongly typed data retrieval and updates without this additional overhead.
When should I use DataComponents or ItemMeta?
You would want to use ItemMeta if you:
- Are doing only simple changes to
ItemStacks - Want to keep cross-version compatibility with your plugin
You would want to use data components if you:
- Want more complicated
ItemStackmodifications - Do not care about cross-version compatibility
- Want to access default (prototype) values
- Want to remove components from an
ItemStack’s prototype
Basic usage
The data component API will fetch values according to the behavior seen in game. So, if the patch removes the minecraft:tool component, trying to get that component will return null.
Retrieving a prototype value
// Get the default durability of diamond sword
int defaultDurability = Material.DIAMOND_SWORD.getDefaultData(DataComponentTypes.MAX_DAMAGE);
Checking for a data component
// Check if this item has a custom name data component
boolean hasCustomName = stack.hasData(DataComponentTypes.CUSTOM_NAME);
logger.info("Has custom name? " + hasCustomName);
Reading a valued data component
// The damage of an item can be null, so we require a null check
Integer damageValue = stack.getData(DataComponentTypes.DAMAGE);
if (damageValue != null) {
logger.info("Current damage: " + damageValue);
} else {
logger.info("This item doesn't have a damage component set.");
}
// Certain components, like the max stack size, will always be present on an item
Integer maxStackSize = stack.getData(DataComponentTypes.MAX_STACK_SIZE);
Setting a valued data component
// Set a custom model data value on this item
stack.setData(DataComponentTypes.CUSTOM_MODEL_DATA,
CustomModelData.customModelData()
.addFloat(0.5f)
.addFlag(true)
.build());
Removing or resetting a data component
// Remove an existing component (e.g. tool)
stack.unsetData(DataComponentTypes.TOOL);
// Reset a component to the default (prototype) value for its item type (e.g. max stack size)
stack.resetData(DataComponentTypes.MAX_STACK_SIZE);
Non-valued data components
Some components are only flags and don’t carry any sort of value:
// Make the item a glider to be used like elytra (combined with the equippable component)
stack.setData(DataComponentTypes.GLIDER);
// Remove the glider flag
stack.unsetData(DataComponentTypes.GLIDER);
Advanced usage with builders
Many data components have complex structures that require builders.
Modifying prototype component values
ItemStack helmet = ItemStack.of(Material.DIAMOND_HELMET);
// Get the equippable component for this item, and make it a builder.
// Note: Not all types have .toBuilder() methods
// This is the prototype value of the diamond helmet.
Equippable.Builder builder = helmet.getData(DataComponentTypes.EQUIPPABLE).toBuilder();
// Make the helmet look like netherite
// We get the prototype equippable value from NETHERITE_HELMET
builder.assetId(Material.NETHERITE_HELMET.getDefaultData(DataComponentTypes.EQUIPPABLE).assetId());
// And give it a spooky sound when putting it on
builder.equipSound(SoundEventKeys.ENTITY_GHAST_HURT);
// Set our new item
helmet.setData(DataComponentTypes.EQUIPPABLE, builder);
This will create a diamond helmet that looks like a netherite helmet and plays a spooky ghast sound when equipped.
Example: Written book
ItemStack book = ItemStack.of(Material.WRITTEN_BOOK);
WrittenBookContent.Builder builder = WrittenBookContent.writtenBookContent("My Book", "AuthorName");
// Add a page
builder.addPage(Component.text("This is a new page!"));
// Add a page that shows differently for people who have swear filtering on
// Players who have disabled filtering, will see "I hate Paper!", while those with filtering on will see the "I love Paper!".
builder.addFilteredPage(Filtered.of(Component.text("I hate Paper!"), Component.text("I love Paper!")));
// Change generation
builder.generation(1);
// Apply changes
book.setData(DataComponentTypes.WRITTEN_BOOK_CONTENT, builder.build());
Example: Cool sword
ItemStack sword = ItemStack.of(Material.DIAMOND_SWORD);
sword.setData(DataComponentTypes.LORE,
ItemLore.lore()
.addLine(Component.text("Cool sword!"))
.build());
sword.setData(DataComponentTypes.ENCHANTMENTS,
ItemEnchantments.itemEnchantments()
.add(Enchantment.SHARPNESS, 10)
.build());
sword.setData(DataComponentTypes.RARITY, ItemRarity.RARE);
sword.unsetData(DataComponentTypes.TOOL); // Remove the tool component
sword.setData(DataComponentTypes.MAX_DAMAGE, 10);
sword.setData(DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE, true); // Make it glow!
Matching items without certain data components
When comparing items, you sometimes want to ignore certain values. For this, we can use the ItemStack#matchesWithoutData method.
For example, here we compare two diamond swords whilst ignoring their durability:
ItemStack originalSword = ItemStack.of(Material.DIAMOND_SWORD);
ItemStack damagedSword = ItemStack.of(Material.DIAMOND_SWORD);
damagedSword.setData(DataComponentTypes.DAMAGE, 100);
boolean match = damagedSword.matchesWithoutData(originalSword, Set.of(DataComponentTypes.DAMAGE), false);
logger.info("Do the sword match? " + match);
// -> true
Debugging your plugin
Debugging your plugin is vital to being able to fix bugs and issues in your plugin. This page will cover some of the most common debugging techniques.
Printing to the console
One of the most common debugging techniques is to print to the console. This is likely something you’ve done before, as it’s very simple. This has a few downsides, though. It can be hard to find the print statements in the console, and it can be hard to remove them all when you’re done debugging. Most notably, you have to recompile your plugin and restart the server to add or remove debugging.
When debugging, you can use System.out.println(""); to print to the console. It is recommended to use your plugin’s logger instead though, as it will be easier to know which plugin the log has come from. This can be done simply with:
plugin.getComponentLogger().debug(Component.text("SuperDuperBad Thing has happened"));
Logger Levels
In some consoles, using the warning level will print the message in different colors. This can be useful for finding your print statements in the console.
Using a remote debugger
A debugger is a tool that allows you to pause your code at a certain point and inspect the values of variables. This can be very useful for finding out why your code isn’t working as expected and also for finding out where your code is going wrong.
Setting up the debugger
To use a debugger, you need to set up your IDE to use it. This is different for each IDE, but for the sake of this guide, we will be using IntelliJ IDEA.
To set up a debugger in IntelliJ, you need to create a new run configuration. You can do this by clicking the dropdown next to the run button and clicking Edit Configurations...:
(Image of IntelliJ Edit Configurations)
Then, click the + button in the top left and select Remote JVM Debug. You can then name the configuration whatever you want, and click Apply:
(Image of IntelliJ Remote JVM Debug Configuration)
Finally, copy the command line arguments from the window, and paste these into your server’s startup script. These will go after the java command and before -jar. Once you have done this, you can click OK. For example:
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 -jar paper-1.21.10.jar nogui
Once your server is running, you can use the bug icon in the top right to connect your debugger to the server:
(Image of IntelliJ Debug Button)
Using the debugger
Let’s say we have this code:
@EventHandler
public void onPlayerMove(PlayerMoveEvent event) {
Player player = event.getPlayer();
Location location = player.getLocation();
if (location.getWorld() == null) return;
if (location.getWorld().getEnvironment() == World.Environment.NETHER) {
player.sendMessage("You are in the nether!");
}
}
You can add a breakpoint to the line by clicking on the line number:
(Image of IntelliJ Breakpoint)
This will pause the code when it reaches that line. You can then use the debugger to inspect the values of variables:
(Image of IntelliJ Debugger)
You can inspect the values of each of the variables in the current scope. You can also use the buttons in the top to step from one breakpoint to the next. If needed, you can also use the text box at the top to evaluate expressions for debugging purposes.
Using direct debugging
Direct debugging will allow you to run the server directly from your IDE, and will allow you to use breakpoints and step through your code.
We can achieve this by using JPenilla’s Gradle plugin to run the server directly from the IDE.
See here for instructions on how to set up the plugin.
Display entities
Added in 1.19.4, display entities are a powerful way to display various things in the world, like blocks, items and text. By default, these entities have no hitbox, don’t move, make sounds or take damage, making them the perfect for all kinds of applications, like holograms.
Types
Text
Text can be displayed via a TextDisplay entity.
TextDisplay display = world.spawn(location, TextDisplay.class, entity -> {
// customize the entity!
entity.text(Component.text("Some awesome content", NamedTextColor.BLACK));
entity.setBillboard(Display.Billboard.VERTICAL); // pivot only around the vertical axis
entity.setBackgroundColor(Color.RED); // make the background red
// see the Display and TextDisplay Javadoc, there are many more options
});
Blocks
Blocks can be displayed
# PaperMC Knowledge Base
## Configuration
### World Configuration
- Includes settings for worlds within the server.
### Global Configuration
- Includes settings that apply to the entire server.
### Configuration Files
* **bukkit.yml**: Configuration file for Bukkit settings.
* **spigot.yml**: Configuration file for Spigot settings.
* **commands.yml**: Configuration file for command aliases and settings.
* **permissions.yml**: Configuration file for managing permissions.
* **help.yml**: Configuration file for custom help messages.
* **server.properties**: Configuration file for basic server settings.
* **Vanilla data files**: Vanilla Minecraft data files.
* **Paper plugins**: Configuration for Paper-specific plugin settings.
* **Commands**: Defines server commands and their properties.
* **System properties**: System-level settings for the server.
* **Permissions**: Defines permissions and their assignments.
* **Miscellaneous**: Other configuration options.
* **Bug fixes**: Configuration related to bug fixes.
* **Frequently asked questions**: Configuration related to frequently asked questions.
## Development
### Getting Started
* Project Setup: Setting up the development environment.
* How Plugins Work: Explanation of plugin functionality.
* Paper Plugins: Information about Paper-specific plugins.
### Experimental Features
* plugin.yml: Plugin configuration file.
* paperweight-userdev: Information about paperweight user development.
### API
#### Command API
* **Basics**
* Introduction: Overview of the command API.
* Command Trees: Structuring commands in a tree-like format.
* Arguments and Literals: Defining command arguments and literals.
* Executors: Handling command execution.
* Registration: Registering commands with the server.
* Requirements: Setting requirements for command usage.
* Suggestions: Providing command suggestions.
* Custom Arguments: Creating custom argument types.
* **Arguments**
* Minecraft-Specific: Arguments related to Minecraft entities and objects.
* Location: Handling location-based arguments.
* Entities and Players: Specifying entities and players as arguments.
* Registry: Using Minecraft registries in arguments.
* Paper-Specific: Arguments specific to Paper.
* Enums: Using enums as arguments.
* Predicates: Using predicates for argument validation.
* Adventure: Using the Adventure library for command components.
* Miscellaneous: Other command API features.
* Basic commands: Basic command examples.
* Comparison: Command comparisons.
#### Component API
* Introduction: Overview of the Component API.
* Internationalization: Supporting multiple languages.
* Audiences: Sending messages to specific audiences.
* Signed Messages: Handling signed messages.
#### Event API
* Listeners: Creating event listeners.
* Custom Events: Defining custom events.
* Handler Lists: Managing event handlers.
* Chat Events: Handling chat-related events.
* Entity API: Interacting with entities.
* Teleportation: Managing entity teleportation.
* Display entities: Using display entities to show blocks, items, and text.
* Inventories: Managing inventories.
* Menu Type API: Creating custom menu types.
* Experimental: This API is experimental and may change.
* Custom InventoryHolders: Managing custom inventory holders.
#### Lifecycle API
* Introduction: Overview of the Lifecycle API.
* Datapack Discovery: Discovering and managing datapacks.
* Experimental: This API is experimental and may change.
* Data Components: Manipulating item data.
* Experimental: This API is experimental and may change.
* Persistent Data Container (PDC): Storing persistent data.
* Scheduling: Scheduling tasks within the plugin.
* Plugin Messaging: Sending messages between plugins.
* Plugin Configuration: Managing plugin configuration.
* Registries
* Experimental: This API is experimental and may change.
* Dialog API
* Experimental: This API is experimental and may change.
#### Dialog API
* **Overview:** Introduction to the Dialog API.
* **What is a dialog?** Dialogs are custom in-game menus for displaying information or gathering user input.
* **Showing dialogs:** Use `/dialog show <players> <dialog>` command or `Audience#showDialog(DialogLike)`. Get built-in dialogs from the `Dialog` interface or create new ones with `Dialog#create`.
* **Built-in dialogs:**
* Server Links
* Quick Actions
* Custom Options
* **Adding server links:** Retrieve the `ServerLinks` instance from `Bukkit.getServer().getServerLinks()` and use its methods.
* **Creating dialogs dynamically:** Use the `Dialog#create` method. Dialogs require a base and a type.
* **Dialog base:** Created using `DialogBase.builder(Component title)`. Can declare:
* `afterAction(DialogAfterAction)`: Action after dialog is closed.
* `canCloseWithEscape(boolean)`: Whether the dialog can be closed with the `esc` key.
* `externalTitle(Component)`: Title for buttons that open this dialog.
* `body(List<? extends DialogBody>)`: The body of the dialog.
* `inputs(List<? extends DialogInput>)`: The inputs of the dialog.
* **Dialog body:** Can contain `DialogBody.plainMessage(Component)` for text or `DialogBody.item(ItemStack)` for items.
* **Dialog input:**
* `DialogInput.bool`: Tick box (true/false).
* `DialogInput.singleOption`: Multiple-choice button.
* `DialogInput.text`: String input field.
* `DialogInput.numberRange`: Slider for number input.
* **Dialog type:**
* `notice()`: Simple dialog with one button.
* `confirmation(ActionButton yesButton, ActionButton noButton)`: Dialog with yes/no buttons.
* `dialogList(RegistrySet dialogs)`: Dialog for opening specified dialogs.
* `multiAction(List<ActionButton> actions)`: Dialog for displaying multiple buttons.
* `serverLinks(ActionButton exitAction, int columns, int buttonWidth)`: Server links dialog.
* **Registering dialogs in the registry:** Register during a registry modification lifecycle event in your plugin's bootstrapper. Allows referencing the dialog in commands.
* **Closing dialogs:**
* `Adventure#closeDialog()`: Closes the dialog and returns to the previous screen.
* `Player#closeInventory()`: Closes the dialog and any other open screens.
* **Example: A blocking confirmation dialog:** Shows a dialog before allowing a player to join.
* **The dialog:** A confirmation dialog registered in the bootstrapper.
* **Requiring the player to agree before allowing them to join:** Uses a `CompletableFuture` to await a response.
* **Example: Retrieving and parsing user input:** Shows a dialog with number range inputs.
* **Reading the input:** Listen to `PlayerCustomClickEvent` and retrieve values from `DialogResponseView`.
* **Using callbacks:** Use `DialogAction.customClick(DialogActionCallback, ClickCallback.Options)` to register a callback locally.
#### Data Components
* Experimental API for accessing and manipulating item data not representable by ItemMeta.
* Allows reading and modifying properties of an item in a stable, object-oriented manner.
* **Introduction**
* A data component represents a piece of data associated with an item (e.g., custom model data, loot contents).
* **Structure**
* **Prototype (default values):** Initial set of components defined on the ItemType of the ItemStack.
* **Patch:** Modifications made to the item (e.g., custom name, enchantments). Applied on top of the prototype. Allows for removing components from the prototype.
* **Differences compared to ItemMeta**
* ItemMeta doesn't represent the prototype/patch relationship.
* **Key differences**
* **Expanded data model:** Exposes a broader and more detailed set of item properties.
* **Version-specific:** Designed to adapt to version changes, may experience breaking changes on updates.
* **Builders and immutability:** Complex components require a builder approach. All data types returned by the API are immutable.
* **Patch-only:** ItemMeta only represents the patch, not the original properties (prototype).
* **No snapshots:** Data component API integrates directly with ItemStack for explicit, strongly typed data retrieval.
* **When should I use DataComponents or ItemMeta?**
* Use ItemMeta for simple changes and cross-version compatibility.
* Use DataComponents for complicated modifications, accessing prototype values, and removing components.
* **Basic usage**
* Fetches values according to in-game behavior.
* **Retrieving a prototype value:** Get default values directly from the Material.
* **Checking for a data component:** Use `stack.hasData(DataComponentTypes.COMPONENT_NAME)`.
* **Reading a valued data component:** Use `stack.getData(DataComponentTypes.COMPONENT_NAME)`. Requires null check.
* **Setting a valued data component:** Use `stack.setData(DataComponentTypes.COMPONENT_NAME, value)`.
* **Removing or resetting a data component:** Use `stack.unsetData(DataComponentTypes.COMPONENT_NAME)` to remove, `stack.resetData(DataComponentTypes.COMPONENT_NAME)` to reset to default.
* **Non-valued data components:** Some components are flags (e.g., `DataComponentTypes.GLIDER`).
* **Advanced usage with builders**
* Many components require builders for complex structures.
* **Modifying prototype component values:** Use `.toBuilder()` to modify the prototype.
* **Example: Written book:** Demonstrates creating a written book with custom content and filtering.
* **Example: Cool sword:** Demonstrates adding lore, enchantments, and removing the tool component.
* **Matching items without certain data components:** Use `ItemStack#matchesWithoutData` to compare items ignoring specific components.
* Recipes
* Particles
* Supporting Paper and Folia
* Roadmap
* Miscellaneous
* Using databases
* Debugging your plugin
* Minecraft internals
* Reading stacktraces
* Contributing
* Events
* GitHub
* Javadoc
* Discord
#### Custom InventoryHolders
* **Overview:** `InventoryHolder`s are a way to identify your plugin's inventories in events.
* **Why use an InventoryHolder?** Simplifies identifying inventories created by your plugin, more reliable than using inventory names.
* **Creating a custom holder:** Implement the `InventoryHolder` interface. Create a class that creates the `Inventory` in the constructor.
* **Opening the inventory:** Instantiate the `InventoryHolder` class and open the inventory for the player using `player.openInventory(inventoryHolder.getInventory())`.
* **Listening to an event:** Listen to inventory events and check if `Inventory#getHolder()` returns an instance of your custom `InventoryHolder`.
* **Storing data on the holder:** Add fields and methods to your `InventoryHolder` class to store extra data for your inventories.
#### Debugging Your Plugin
* **Overview:** Debugging is crucial for fixing bugs in your plugin.
* **Printing to the console:**
* Use `plugin.getComponentLogger().debug(Component.text("message"))` instead of `System.out.println("")`.
* Using `warning` level in the logger can make messages stand out.
* **Using a remote debugger:**
* A debugger allows you to pause code and inspect variables.
* **Setting up the debugger:**
* Create a new "Remote JVM Debug" run configuration in your IDE.
* Copy the command line arguments from the IDE and paste them into your server's startup script (after `java` and before `-jar`).
* **Using the debugger:**
* Add breakpoints by clicking on the line number in your IDE.
* Inspect variables and step through code using the debugger controls.
* **Using direct debugging:**
* Run the server directly from your IDE using a Gradle plugin.
#### Display Entities
* Added in 1.19.4, display entities are a powerful way to display blocks, items and text.
* By default, these entities have no hitbox, don’t move, make sounds or take damage.
* **Types:**
* **Text:** Display text via a `TextDisplay` entity.
* **Blocks:** Display blocks via a `BlockDisplay` entity.
* **Items:** Display items via an `ItemDisplay` entity. Can also display blocks, with the difference being the position in the model.
* **Transformation:**
* Displays can have an arbitrary affine transformation applied to them.
* Transformations are applied in this order: Scale, Rotation, Translation.
* Visualizing transformations: Use the Transformation Visualizer website for quick prototyping!
* **Scale:** Scale the display entity.
* **Rotation:** Rotate the display entity.
* **Translation:** Apply a position offset to the display entity.
* **Interpolation:**
* Transformations and teleports can be linearly interpolated by the client to create a smooth animation.
* **Transformation:** Smoothly rotate a block/item/text in-place.
* **Teleportation:** Interpolate the movement of the entire display entity between two points.
* **Use cases:**
* Displays have many different use cases, ranging from stationary decoration to complex animation.
* Make a decoration that’s visible to only specific players using `Entity#setVisibleByDefault()` and `Player#showEntity()` / `Player#hideEntity()`.
* They can also be added as passengers to entities with the `Entity#addPassenger()` / `Entity#removePassenger()` methods, useful for making styled name tags!
* If the display is only used temporarily, its persistence can be disabled with `Entity#setPersistent()`.
* Make sure to remove the display afterward with `Entity#remove()`.
```markdown
# Teleportation
Entities can be instantaneously teleported to specific positions, synchronously and asynchronously with the `teleport` and `teleportAsync` API.
## Performance
If you expect to teleport into unloaded chunks, it is recommended to use the `teleportAsync` API, as it avoids doing synchronous chunk loads, which put a lot of stress on the server’s main thread - hurting overall performance.
```java
entity.teleport(location);
// loads chunks synchronously and teleports the entity
entity.teleportAsync(location)
.thenAccept(success -> {
// loads chunks asynchronously and teleports the entity
// this code is ran when the teleport completes
// the Future is completed on the main thread, so it is safe to use the API here
if (success) {
// the entity was teleported successfully!
}
});
Danger: You should NEVER call .get() / .join() on the teleportAsync Future on the main thread, as it WILL deadlock your server if the chunk you're teleporting into is not loaded.
Look at
The lookAt API allows you to make a player look at a certain position or entity.
player.lookAt(position, LookAnchor.EYES // the player's eyes will be facing the position);
player.lookAt(entity, LookAnchor.EYES // the player's eyes will be facing the entity
, LookAnchor.FEET // the player will be facing the entity's feet);
Teleport flags
Teleport flags offer a way to teleport entities whilst being able to customize behavior. This allows you to do things like teleport players using relative flags and being able to retain passengers. All available teleport flags can be found in the TeleportFlag class.
Relative teleportation
Teleport a player relatively, preventing velocity from being reset in the X, Y and Z axes.
player.teleport(location, TeleportFlag.Relative.VELOCITY_X, TeleportFlag.Relative.VELOCITY_Y, TeleportFlag.Relative.VELOCITY_Z);
Retaining passengers
Teleport an entity with the RETAIN_PASSENGERS flag, allowing its passengers to be transferred with the entity.
entity.teleport(location, TeleportFlag.EntityState.RETAIN_PASSENGERS);
Supporting Paper and Folia
Folia is a fork of Paper, which is currently maintained by the PaperMC team. It adds the ability to split the world into regions.
Checking for Folia
Depending on what platform your plugin is running on, you may need to implement features differently. For this, you can use this utility method to check if the current server is running Folia:
private static boolean isFolia() {
try {
Class.forName("io.papermc.paper.threadedregions.RegionizedServer");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
Schedulers
In order to support Paper and Folia, you must use the correct scheduler. Folia has different types of schedulers that can be used for different things:
- Global
- Region
- Async
- Entity
If you use these schedulers when running Paper, they will be internally handled to provide the same functionality as if you were running Folia.
Global scheduler
The tasks that you run on the global scheduler will be executed on the global region. You should use this scheduler for any tasks that do not belong to any particular region. These can be fetched with:
GlobalRegionScheduler globalScheduler = server.getGlobalRegionScheduler();
Region scheduler
The region scheduler will be in charge of running tasks for the region that owns a certain location. Do not use this scheduler for operations on entities, as this scheduler is tied to the region. Each entity has its own scheduler which will follow it across regions.
Example: Setting a block to a beehive.
Location locationToChange = ...;
RegionScheduler scheduler = server.getRegionScheduler();
scheduler.execute(plugin, locationToChange, () -> {
locationToChange.getBlock().setType(Material.BEEHIVE);
});
We pass the location as a parameter to the RegionScheduler as it needs to work out which region to execute on.
Async scheduler
The async scheduler can be used for running tasks independent of the server tick process. This can be fetched with:
AsyncScheduler asyncScheduler = server.getAsyncScheduler();
Entity scheduler
Entity schedulers are used for executing tasks on an entity. These will follow the entity wherever it goes, so you must use these instead of the region schedulers.
EntityScheduler scheduler = entity.getScheduler();
Listeners
Events are an efficient way to listen for specific actions that happen in the game. They can be called by the server or by plugins. Plugins are able to call custom events, such as a player completing a quest, for other plugins to listen for.
Your listener class
To listen for events, you need to create a class that implements Listener. It is recommended to name it something related to the events you are listening for.
public class ExampleListener implements Listener {
// ...
}
@EventHandler
To listen for an event, you need to create a method that is annotated with @EventHandler. This method can be named anything you want, but it is recommended to name it something meaningful related to the event it is listening for.
The listener method
The method body does not need to return any data, for this reason, use void as the return type. Listeners take in a single parameter, which is the event that is being listened to.
public class ExampleListener implements Listener {
@EventHandler
public void onPlayerMove(PlayerMoveEvent event) {
// ...
}
}
Events
There is no list of events that can be listened to, however take a look to see all classes that extend Event. An event can only be listened to if it has a static getHandlerList method.
Registering the listener
To register the listener, you need to call Bukkit.getPluginManager().registerEvents() and pass in your listener class instance and an instance of your plugin. This is commonly done in the onEnable() method of your plugin.
public class ExamplePlugin extends JavaPlugin {
@Override
public void onEnable() {
getServer().getPluginManager().registerEvents(new ExampleListener(), this);
}
}
Event priority
You can also specify the priority of the event.
public class ExampleListener implements Listener {
@EventHandler(priority = EventPriority.HIGH)
public void onPlayerMove(PlayerMoveEvent event) {
// ...
}
}
There are six different priorities that you can use:
EventPriority.LOWESTEventPriority.LOWEventPriority.NORMALEventPriority.HIGHEventPriority.HIGHESTEventPriority.MONITOR
The higher the priority, the later the event is called. If it is important that your plugin has the last say in a certain event, you should use EventPriority.HIGHEST.
Note: The MONITOR priority is used to monitor the event, but not change it. It is called after all other priorities have been called.
Event cancellation
Some events can be cancelled, preventing the given action from being completed. These events implement Cancellable.
public class ExampleListener implements Listener {
@EventHandler
public void onPlayerMove(PlayerMoveEvent event) {
event.setCancelled(true);
}
}
Caution: It is important to consider that another plugin could have cancelled or changed the event before your plugin is called. Always check the event before doing anything with it.
Once an event is cancelled, it will continue to call any other listeners for that event unless they add ignoreCancelled = true to the @EventHandler annotation to ignore cancelled events.
public class ExampleListener implements Listener {
@EventHandler(ignoreCancelled = true)
public void onPlayerMove(PlayerMoveEvent event) {
// ...
}
}
Handler lists
Every Event that can be listened to has a HandlerList containing all the listeners that are listening to that event. This list is used to call the listeners when the event is called.
Getting the handler list for an event
To get the handler list for an event, you can call getHandlerList() on the specific event class.
public class ExampleListener implements Listener {
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
HandlerList handlerList = event.getHandlerList();
// ...
}
// Or:
public ExampleListener() {
// Access the handler list through the static getter
HandlerList handlerList = PlayerJoinEvent.getHandlerList();
// ...
}
}
Unregistering a listener
To unregister a listener, you can call unregister() on the HandlerList that the listener is registered to.
public class ExampleListener implements Listener {
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
HandlerList handlerList = event.getHandlerList();
handlerList.unregister(this);
// ...
}
// Or:
public ExampleListener() {
// Access the handler list through the static getter
HandlerList handlerList = PlayerJoinEvent.getHandlerList();
handlerList.unregister(this);
// Granted this is a pretty stupid example...
}
}
You can unregister based on Listener or Plugin for more convenience.
Likewise, you can also unregister all listeners for a specific event by calling unregisterAll() on the HandlerList.
Paper plugins
Paper plugins allow developers to take advantage of more modern concepts introduced by Mojang, such as datapacks, to expand the field of what the Paper API is able to introduce.
Experimental: This is experimental and may be subject to change.
How do I use them?
Similarly to Bukkit plugins, you have to introduce a paper-plugin.yml file into your JAR resources folder. This will not act as a drop-in replacement for plugin.yml, as some things need to be declared differently. You still have the ability to include both paper-plugin.yml and plugin.yml in the same JAR.
Example configuration:
name: Paper-Test-Plugin
version: '1.0'
main: io.papermc.testplugin.TestPlugin
description: Paper Test Plugin
api-version: '1.21.10'
bootstrapper: io.papermc.testplugin.TestPluginBootstrap
loader: io.papermc.testplugin.TestPluginLoader
Dependency declaration
Paper plugins change how to declare dependencies in your paper-plugin.yml:
dependencies:
bootstrap:
# Let's say that RegistryPlugin registers some data that your plugin needs to use
# We don't need this during runtime, so it's not required in the server section.
# However, can be added to both if needed
RegistryPlugin:
load: BEFORE
required: true
join-classpath: true # Defaults to true
server:
# Add a required "RequiredPlugin" dependency, which will load AFTER your plugin.
RequiredPlugin:
load: AFTER
required: true
# This means that your plugin will not have access to their classpath
join-classpath: false
With Paper plugins, dependencies are split into two sections:
- bootstrap: Dependencies that you will be using in the
bootstrap. - server: Dependencies that are used for the core functionality of your plugin, whilst the server is running.
Let’s take a look at a dependency:
RegistryPlugin:
load: BEFORE # Defaults to OMIT
required: true # Defaults to true
join-classpath: true # Defaults to true
load(BEFORE|AFTER|OMIT): Whether this plugin should load before or after your plugin. Note:OMIThas undefined ordering behavior.required: Whether this plugin is required for your plugin to load.join-classpath: Whether your plugin should have access to their classpath. This is used for plugins that need to access other plugins internals directly.
Cyclic Loading: In certain cases, plugins may be able to introduce cyclic loading loops, which will prevent the server from starting. Please read the cyclic loading guide for more information.
Examples:
# Suppose we require ProtocolLib to be loaded for our plugin
ProtocolLib:
load: BEFORE
required: true
# Now, we are going to register some details for a shop plugin
# So the shop plugin should load after our plugin
SuperShopsXUnlimited:
load: AFTER
required: false
# Now, we are going to need to access a plugins classpath
# So that we can properly interact with it.
SuperDuperTacoParty:
required: true
join-classpath: true
What is it used for?
Paper plugins lay down the framework for some future API. Our goals are to open more modern API that better aligns with Vanilla. Paper plugins allow us to do just that by making a new way to load plugin resources before the server has started by using bootstrappers.
Bootstrapper
Paper plugins are able to identify their own bootstrapper by implementing PluginBootstrap and adding the class of your implementation to the bootstrapper field in the paper-plugin.yml.
public class TestPluginBootstrap implements PluginBootstrap {
@Override
public void bootstrap(BootstrapContext context) {
}
@Override
public JavaPlugin createPlugin(PluginProviderContext context) {
return new TestPlugin("My custom parameter");
}
}
A bootstrapper also allows you to change the way your plugin is initialized, allowing you to pass values into your plugin constructor.
Currently, bootstrappers do not offer much new API and are highly experimental. This may be subject to change once more API is introduced.
Loaders
Paper plugins are able to identify their own plugin loader by implementing PluginLoader and adding the class of your implementation to the loader field in the paper-plugin.yml.
The goal of the plugin loader is the creation of an expected/dynamic environment for the plugin to load into. This, as of right now, only applies to creating the expected classpath for the plugin, e.g. supplying external libraries to the plugin.
public class TestPluginLoader implements PluginLoader {
@Override
public void classloader(PluginClasspathBuilder classpathBuilder) {
classpathBuilder.addLibrary(new JarLibrary(Path.of("dependency.jar")));
MavenLibraryResolver resolver = new MavenLibraryResolver();
resolver.addDependency(new Dependency(new DefaultArtifact("com.example:example:version"), null));
resolver.addRepository(new RemoteRepository.Builder("paper", "default", "https://repo.papermc.io/repository/maven-public/").build());
classpathBuilder.addLibrary(resolver);
}
}
Currently, you are able to add two different library types: JarLibrary and MavenLibraryResolver.
Danger: If you wish to resolve libraries from Maven Central, use a mirror, as using Maven Central directly as a CDN is against the Maven Central Terms of Service, and users of your plugin may hit rate limits.
You should use Paper’s default mirror, configured by the PAPER_DEFAULT_CENTRAL_REPOSITORY environment variable and org.bukkit.plugin.java.LibraryLoader.centralURL system property:
resolver.addRepository(new RemoteRepository.Builder("central", "default", MavenLibraryResolver.MAVEN_CENTRAL_DEFAULT_MIRROR).build());
Using the Maven Central repository (i.e. *.maven.org or *.maven.apache.org) will cause a warning to be shown in the console.
Differences
Bukkit serialization system
Paper plugins still support the serialization system (org.bukkit.configuration.serialization) that Bukkit uses. However, custom classes will not be automatically registered for serialization. In order to use ConfigurationSection#getObject, you must call ConfigurationSerialization#registerClass(Class) before you attempt to fetch objects from configurations.
Classloading isolation
Paper plugins are not able to access each other unless given explicit access by depending on another plugin, etc. This helps prevent Paper plugins from accidentally accessing each other’s dependencies, and in general helps ensure that plugins are only able to access what they explicitly depend on.
Paper plugins have the ability to bypass this, being able to access OTHER plugins’ classloaders by adding a join-classpath option to their paper-plugin.yml.
Plugin:
join-classpath: true # Means you have access to their classpath
Note, other Paper plugins will still be unable to access your classloader.
Load order logic split
In order to better take advantage of classloading isolation, Paper plugins do not use the dependencies field to determine load order. This was done for a variety of reasons, mostly to allow better control and allow plugins to properly share classloaders. See declaring dependencies for more information on how to declare the load order of your plugin.
Commands
Paper plugins do not use the commands field to register commands. This means that you do not need to include all of your commands in the paper-plugin.yml file. Instead, you can register commands using the Brigadier Command API.
Cyclic plugin loading
Cyclic loading describes the phenomenon when a plugin loading causes a loop that eventually cycles back to the original plugin.
Unlike Bukkit plugins, Paper plugins will not attempt to resolve cyclic loading issues.
However, if Paper detects a loop that cannot be resolved, you will get an error that looks like this:
[ERROR]: [LoadOrderTree] =================================
[ERROR]: [LoadOrderTree] Circular plugin loading detected:
[ERROR]: [LoadOrderTree] 1) Paper-Test-Plugin1 -> Paper-Test-Plugin -> Paper-Test-Plugin1
[ERROR]: [LoadOrderTree] Paper-Test-Plugin1 loadbefore: [Paper-Test-Plugin]
[ERROR]: [LoadOrderTree] Paper-Test-Plugin loadbefore: [Paper-Test-Plugin1]
[ERROR]: [LoadOrderTree] Please report this to the plugin authors of the first plugin of each loop or join the PaperMC Discord server for further help.
[ERROR]: [LoadOrderTree] =================================
It is up to you to resolve these cyclical loading issues.
How plugins work
Plugins are a way to extend the functionality of a Minecraft server. They are written in JVM-based languages such as Java, Kotlin, Groovy or Scala. Plugins are loaded from the plugins folder in the server directory. Plugins will be loaded from a .jar file. Each plugin has a main class that is specified in the plugin’s plugin.yml file. This class must extend JavaPlugin, and is the entry point for the plugin and is where the plugin’s lifecycle methods are defined.
Caution: We do not recommend writing code inside your main class’s constructor as there are no guarantees about what API is available at that point. Instead, you should use the onLoad method to initialize your plugin. Also, do not call your plugin’s constructor directly. This will cause issues with your plugin.
Plugin lifecycle
Plugins are loaded and unloaded at runtime. When a plugin is loaded, it is initialized and enabled. When a plugin is unloaded, it is disabled and finalized.
Initialization
When a plugin is loaded, it is initialized. This means that the plugin is loaded into memory and its onLoad method is called. This method is used to initialize the plugin and set up any resources that it needs. Most of the Bukkit API is not available at this point, so it is not safe to interact with it.
Enabling
When a plugin is enabled, its onEnable method is called. This method is used to set up any resources that the plugin needs to run. This method is called when the plugin is initialized but before the server has started ticking, so it is safe to register event listeners and other resources that the plugin needs to run, however often not safe to interact with a lot of APIs.
This is when you can also open database connections, start threads, and other things that are not safe to do in the onLoad method.
Disabling
When a plugin is disabled, its onDisable method is called. This method is used to clean up any resources that the plugin has allocated. This method is called before all plugins are unloaded, and is meant for any cleanup that needs to be done before the plugin is unloaded. This may include saving data to disk or closing connections to databases.
Event listeners
Events are a way for plugins to listen to things that happen in the server and run code when they are fired. For example, PlayerJoinEvent is fired when a player joins the server. This is a more performant way to run code when something happens, as opposed to constantly checking. See our event listener page for more.
Some events are cancellable. This means that when the event is fired, it can be cancelled which negates or stops the effect of the event. For example, PlayerMoveEvent is cancellable. This means that when it is cancelled, the player will not move. This is useful for things like anti-cheat, where you want to cancel the event if the player is moving too fast.
It is important to think about how “hot” an event is when writing event listeners. A “hot” event is an event that is fired very often. For example, PlayerMoveEvent is fired every time a player moves. This means that if you have a lot of expensive code in your event listener, it will be run every time a player moves. This can cause a lot of lag. It is important to keep event listeners as lightweight as possible. One possible way is to quickly check if the event should be handled, and if not, return. For example, if you only want to handle the event if the player is moving from one block to another, you can check if the player’s location has changed blocks. If it hasn’t, you can return from the listener.
Commands
Commands are a way for players, the console, RCON and command blocks to run code on the server. Commands are registered by plugins and can be run by command senders. For example, the /help command is registered by the server and can be run by players. Commands can be run by players by typing them in the chat or by running them from a command block.
Commands can have arguments. For example, the /give command takes an argument for the player to give the item to and an argument for the item to give. Arguments are separated by spaces. For example, the command /give Notch diamond will give the player named Notch a diamond. Note here that the arguments are ["Notch", "diamond"].
Permissions
Permissions are a way to control who can run commands and who can listen to events. Permissions are registered by plugins and can be checked by other plugins. Permissions can be granted to players and groups. Permissions can have a hierarchical nature, if defined so by the plugin in their plugin.yml. For example, a plugin can define example.command.help as a sub-permission of example.command. This means that if a player has the example.command permission, they will also have the example.command.help permission.
Note: Permission plugins can allow the usage of wildcard permissions using the * character to grant any permission or sub-permission available, allowing hierarchical permissions even if not set by the plugin itself. For example, granting example.command.* through a permission plugin with wildcard support will grant access to all permissions starting with example.command itself.
It is not recommended to use wildcard permissions, especially * (All permissions), as it can be a huge security risk, as well as potentially causing unwanted side effects to a player. Use with caution.
Configuration
Plugins can have configuration files. These files are used to store data that the plugin needs to run. For example, a plugin that adds a new block to the game might have a configuration file that stores the block’s ID. Configuration files should be stored in the plugin’s data folder, within the plugins folder. The server offers a YAML configuration API that can be used to read and write configuration files. See here for more information.
Scheduling tasks
Plugins can schedule tasks to run at a later time. This is useful for things like running code after a certain amount of time has passed. For example, a plugin might want to run code after 5 seconds. This can be done by scheduling a task to run after 100 ticks - one second is 20 ticks during normal operation. It is important to note that tasks might be delayed if the server is lagging. For example, if the server is only running at 10 ticks per second, a task that is scheduled to run after 100 ticks will take 10 seconds.
In Java, typically you could use Thread#sleep() to delay the execution of code. However, if the code is running on the main thread, this will cause the server to pause for the delay. Instead, you should use the Scheduler API to schedule tasks to run later.
Learn more about the Scheduler API.
Components
Since Minecraft 1.7 and the introduction of “components”, plugins can now send messages to players that contain rich text. This means that plugins can send messages that contain things like colors, bold text, and clickable links. Colors were always possible, but only through the use of legacy color codes.
Paper implements a library called Adventure that makes it easy to create and send messages to players. Learn more about the Adventure API from their docs or our docs here.
Minecraft internals
The code that runs Minecraft is not open source. Bukkit is an API that allows plugins to interact with the server. This is implemented by CraftBukkit and interacts with Minecraft’s code. You will often hear the terms NMS and CraftBukkit when talking about Minecraft internals.
Using Minecraft internals is not recommended. This is because using internal code directly is not guaranteed to be stable and it changes often. This means that your plugin may break when a new version of Minecraft is released. Whenever possible, you should use API instead of internals.
PaperMC will offer no direct support for programming against Minecraft internals.
What is NMS?
NMS stands for net.minecraft.server and refers to a Java package that contains a lot of Mojang’s code. This code is proprietary and is not open source. This code is not guaranteed to be stable when invoked externally and may change at any time.
Accessing Minecraft internals
In order to use Mojang and CraftBukkit code, you may either use the paperweight-userdev Gradle plugin or use reflection. paperweight-userdev is the recommended way to access internal code as it is easier to use due to being able to have the remapped code in your IDE. You can find out more about this in the paperweight-userdev section.
However, if you are unable to use paperweight-userdev, you can use reflection.
Reflection
Reflection is a way to access code at runtime. This allows you to access code that may not be available at compile time. Reflection is often used to access internal code across multiple versions. However, reflection does come with performance impacts if used improperly. For example, if you are accessing a method or field more than once, you should cache the Field / Method to prevent the performance impact of looking up the field/method each time.
1.20.4 and older
The internal CraftBukkit code was relocated to org.bukkit.craftbukkit.<version> unless you ran a Mojang-mapped version of Paper. This was unlikely to be the case in most production environments until 1.20.5. This means that any attempts to reflect had to include the version. For example, org.bukkit.craftbukkit.v1_20_R2.CraftServer was the full class and package name for the CraftServer class in version 1.20.2. You could access these classes easily with some reflection utilities.
private static final String CRAFTBUKKIT_PACKAGE = Bukkit.getServer().getClass().getPackageName();
public static String cbClass(String clazz) {
return CRAFTBUKKIT_PACKAGE + "." + clazz;
}
// You can then use this method to get the CraftBukkit class:
Class.forName(cbClass("entity.CraftBee"));
Minecraft’s code is obfuscated. This means that the names of classes and methods are changed to make them harder to understand. Paper deobfuscates these identifiers for development and since 1.20.5, also for runtime.
1.20.4 and older
Previously, to provide compatibility with legacy plugins, Paper was reobfuscated at runtime. You could use a library like reflection-remapper to automatically remap the reflection references. This allowed you to use the deobfuscated, Mojang-mapped names in your code. This was recommended as it made the code easier to understand.
Mojang-mapped servers
Running a Mojang-mapped (moj-map) server is an excellent way to streamline your processes because you can develop using the same mappings that will be present at runtime. This eliminates the need for remapping in your compilation, which in turn simplifies debugging and allows you to hotswap plugins.
As of 1.20.5, Paper ships with a Mojang-mapped runtime by default instead of reobfuscating the server to Spigot mappings. By adopting Mojang mappings, you ensure that your plugin won’t require internal remapping at runtime.
For more information, see the plugin remapping section and userdev documentation covering these changes.
Getting the current Minecraft version
You can get the current Minecraft version to allow you to use the correct code for a specific version. This can be done with one of the following methods:
// Example value: 1.21.10
String minecraftVersion = Bukkit.getServer().getMinecraftVersion();
// Example value: 1.21.10-R0.1-SNAPSHOT
String bukkitVersion = Bukkit.getServer().getBukkitVersion();
// Example value for 1.20.1: 3465
int dataVersion = Bukkit.getUnsafe().getDataVersion();
Parsing the version
Parsing the version from the package name of classes is no longer possible as of 1.20.5 as Paper stopped relocating the CraftBukkit package. See the reflection section for more information.
```markdown
# How Plugins Work
## Overview
Plugins are a way to extend the functionality of a Minecraft server. They are written in JVM-based languages such as Java, Kotlin, Groovy, or Scala. Plugins are loaded from the `plugins` folder in the server directory, specifically from `.jar` files. Each plugin has a main class, specified in the `plugin.yml` file, which extends `JavaPlugin`. This class serves as the entry point for the plugin and defines its lifecycle methods.
**Caution:** Avoid writing code inside the main class's constructor, as API availability is not guaranteed at that point. Instead, use the `onLoad` method for initialization. Do not call the plugin's constructor directly.
## Plugin Lifecycle
Plugins are loaded and unloaded at runtime.
* **Loading:** The plugin is initialized and enabled.
* **Unloading:** The plugin is disabled and finalized.
### Initialization
When a plugin is loaded, it is initialized, meaning:
* The plugin is loaded into memory.
* Its `onLoad` method is called.
The `onLoad` method is used to:
* Initialize the plugin.
* Set up any resources it needs.
Most of the Bukkit API is not available at this point, so interacting with it is not safe.
### Enabling
When a plugin is enabled:
* Its `onEnable` method is called.
The `onEnable` method is used to:
* Set up resources needed for the plugin to run.
* Register event listeners and other resources.
* Open database connections and start threads.
This method is called when the plugin is initialized but before the server has started ticking.
### Disabling
When a plugin is disabled:
* Its `onDisable` method is called.
The `onDisable` method is used to:
* Clean up any allocated resources.
* Save data to disk.
* Close database connections.
This method is called before all plugins are unloaded.
## Event Listeners
Events allow plugins to listen to actions happening on the server and execute code when these events are triggered.
* Example: `PlayerJoinEvent` is fired when a player joins the server.
Events are more performant than constantly checking for actions.
Some events are cancellable, stopping the effect of the event.
* Example: `PlayerMoveEvent` can be cancelled to prevent player movement.
It's important to consider how "hot" an event is (how often it's fired) and keep event listeners lightweight to avoid lag.
* "Hot" events like `PlayerMoveEvent` fire very often.
* Optimize listeners by quickly checking if the event should be handled and returning if not.
## Commands
Commands are a way for players, the console, RCON, and command blocks to run code on the server.
* Registered by plugins.
* Run by command senders.
* Example: `/help` is registered by the server.
Commands can have arguments, separated by spaces.
* Example: `/give Notch diamond` gives the player named Notch a diamond.
## Permissions
Permissions control who can run commands and listen to events.
* Registered by plugins.
* Checked by other plugins.
* Granted to players and groups.
Permissions can be hierarchical.
* Example: `example.command.help` is a sub-permission of `example.command`.
**Note:** Wildcard permissions (e.g., `example.command.*`) should be used with caution due to security risks and potential side effects.
## Configuration
Plugins can have configuration files to store data.
* Stored in the plugin's data folder within the `plugins` folder.
* The server offers a YAML configuration API for reading and writing configuration files.
## Scheduling Tasks
Plugins can schedule tasks to run at a later time.
* Useful for running code after a certain amount of time has passed.
* Tasks might be delayed if the server is lagging.
Use the `Scheduler` API instead of `Thread#sleep()` to avoid pausing the server.
* One second is 20 ticks during normal operation.
## Components
Plugins can send messages to players with rich text (colors, bold text, clickable links).
* Paper implements the `Adventure` library for creating and sending messages.
---
# Minecraft Internals
## Overview
The code that runs Minecraft is not open source. Bukkit provides an API for plugins to interact with the server, implemented by CraftBukkit. The terms NMS and CraftBukkit are commonly used when discussing Minecraft internals.
**Caution:** Using Minecraft internals is not recommended due to instability and potential breakage with new Minecraft versions. Use the API instead whenever possible. PaperMC offers no direct support for programming against Minecraft internals.
## What is NMS?
NMS stands for `net.minecraft.server` and refers to a Java package containing much of Mojang's code. This code is proprietary, not open source, and not guaranteed to be stable when invoked externally. It may change at any time.
## Accessing Minecraft Internals
Mojang and CraftBukkit code can be accessed using either the `paperweight-userdev` Gradle plugin or reflection.
* `paperweight-userdev` is recommended for easier use with remapped code in your IDE.
* Reflection can be used if `paperweight-userdev` is not an option.
### Reflection
Reflection allows accessing code at runtime, including code that may not be available at compile time. It is often used to access internal code across multiple versions.
* **Performance Impact:** Reflection can have performance impacts if used improperly.
* **Caching:** Cache `Field` / `Method` objects when accessing them more than once to prevent performance issues.
#### 1.20.4 and Older
CraftBukkit code was located at `org.bukkit.craftbukkit.<version>` unless using a Mojang-mapped version of Paper. Reflection required including the version. For example:
`org.bukkit.craftbukkit.v1_20_R2.CraftServer`
Minecraft's code is obfuscated. Paper deobfuscates these identifiers for development and, since 1.20.5, also for runtime.
Previously, Paper was reobfuscated at runtime to provide compatibility with legacy plugins. Libraries like `reflection-remapper` could be used to automatically remap reflection references.
### Mojang-Mapped Servers
Running a Mojang-mapped (moj-map) server streamlines development by using the same mappings at runtime, simplifying debugging and allowing hotswapping of plugins. As of 1.20.5, Paper ships with a Mojang-mapped runtime by default.
## Getting the Current Minecraft Version
You can get the current Minecraft version to use the correct code for a specific version.
```java
// Example value: 1.21.10
String minecraftVersion = Bukkit.getServer().getMinecraftVersion();
// Example value: 1.21.10-R0.1-SNAPSHOT
String bukkitVersion = Bukkit.getServer().getBukkitVersion();
// Example value for 1.20.1: 3465
int dataVersion = Bukkit.getUnsafe().getDataVersion();
Parsing the version from the package name of classes is no longer possible as of 1.20.5.
Lifecycle API
Overview
The Lifecycle API is used for lifecycle-related registration. It is currently used by the Brigadier command API and is planned for use with the Registry Modification API. This API is beneficial for systems initialized very early in the startup process.
LifecycleEventManager
The LifecycleEventManager is tied to either a Plugin instance or a BootstrapContext, depending on where you access it.
Example (Plugin):
@Override
public void onEnable() {
final LifecycleEventManager<Plugin> lifecycleManager = this.getLifecycleManager();
}
Example (Bootstrap):
@Override
public void bootstrap(BootstrapContext context) {
final LifecycleEventManager<BootstrapContext> lifecycleManager = context.getLifecycleManager();
}
LifecycleEvents
After obtaining the LifecycleEventManager, create an event handler by selecting an event type from LifecycleEvents.
Example:
@Override
public void onEnable() {
final LifecycleEventManager<Plugin> lifecycleManager = this.getLifecycleManager();
PrioritizedLifecycleEventHandlerConfiguration<LifecycleEventOwner> config = LifecycleEvents.SOME_EVENT.newHandler((event) -> {
// Handler for the event
});
}
Configuration
Each handler can be configured in several ways, depending on the event type.
Priority
Sets the execution order relative to other handlers of the same event type. Lower numbers are run earlier. The default priority is 0.
Monitor
Marks the handler to be called after all other non-monitor handlers have been called. Use this only to inspect state, not to modify it.
Priority and monitor state are exclusive. Setting one resets the other.
Example:
@Override
public void onEnable() {
final LifecycleEventManager<Plugin> lifecycleManager = this.getLifecycleManager();
PrioritizedLifecycleEventHandlerConfiguration<LifecycleEventOwner> config = LifecycleEvents.SOME_EVENT.newHandler((event) -> {
// Handler for the event
});
config.priority(10); // sets a priority of 10
// or
config.monitor(); // marks the handler as a monitor
}
Registering
After configuring the handler, register it with the lifecycle manager.
Example:
@Override
public void onEnable() {
final LifecycleEventManager<Plugin> lifecycleManager = this.getLifecycleManager();
PrioritizedLifecycleEventHandlerConfiguration<LifecycleEventOwner> config = LifecycleEvents.SOME_EVENT.newHandler((event) -> {
// Handler for the event
}).priority(10);
lifecycleManager.registerEventHandler(config);
}
A shorthand method exists for registering the handler without configuration:
@Override
public void onEnable() {
final LifecycleEventManager<Plugin> lifecycleManager = this.getLifecycleManager();
lifecycleManager.registerEventHandler(LifecycleEvents.COMMANDS, (event) -> {
// Handler for the event
});
}
Note: Some event types have special behaviors that restrict certain mechanics. Plugin reloading (via /bukkit:reload or Server#reload()) is disabled if plugins register handlers in certain situations.
Why Does This Exist?
This API is necessary because some events fire before JavaPlugin instances or the MinecraftServer instance are created, right at the start of server startup. The existing Bukkit event system is not designed to exist at this time.
Technical Explanation:
- Bukkit events cannot have generics due to reflective registration, leading to many useless classes for similar patterns like registry modification events.
- The existing system always has priorities. Lifecycle events may not always want priorities.
- The existing system exists too late, relying on the
Plugininstance, which does not exist during bootstrapping. - A new system allows using interfaces and server implementations for events, simplifying them.
- A new system enforces, at compile time, which events you can register where based on the context of the registration.
Menu Type API
Experimental
The Menu Type API is experimental and may change in the future.
Overview
The Menu Type API allows developers to replicate various Minecraft menu types, such as chests, crafting tables, and villager trade menus, which were not perfectly replicable with the old Bukkit inventory API.
What is a menu?
Menus, also referred to as views, are user interfaces that can be created and viewed by players. The MenuType interface declares all possible menu types.
Menus are the visual representations of inventories, which are the containers.
Menus created using this API follow the same logic as Vanilla. Some menus can directly represent a block, like a furnace, while MERCHANT can represent a merchant entity.
What are inventory views?
An InventoryView is a specific view created from a menu type.
An inventory view links together two separate inventories and always has a player viewing them. The bottom linked inventory is the player’s inventory.
Some views have specialized subinterfaces for quickly checking their type, like FurnaceView. For other views, you can use the InventoryView#getMenuType method.
Building inventory views from menu types
The most common way to create inventory views is by using their respective builders. Every menu type has a builder which can be used to customize the resulting view.
Example:
// MenuType.CRAFTING is used to open a crafting table.
MenuType.CRAFTING.builder()
// Set the title of the view, which will be displayed at the top.
.title(Component.text("The inventory view's title"))
// Determines whether the server should check if the player can reach the location.
.checkReachable(true)
// Set the location. Because of checkReachable being set to `true`, this has to be a valid
// crafting table. The server will check and make sure that the player does not get pushed
// away too far to use the crafting table and will close the player's inventory if the
// crafting table were to be pushed away.
.location(location)
// Build this view for the provided player, linking the inventory of the crafting table
// together with the player's own inventory into an inventory view.
.build(player)
// Open the view.
.open();
Builders can be reused to reduce code repetition.
Opening blocks that have menus
Almost all inventory views have a block attached to them, except for MenuType.MERCHANT, which has a Merchant attached to it.
There are two types of blocks that have menus:
- Block entity blocks (tile entity blocks): Have a state associated with them (e.g., beacon, chests, furnaces).
- Stateless blocks: Do not have any state associated with them (e.g., crafting tables, grindstones, anvils).
When you open a specific location with the #location builder method, and the block matches the expected block from the menu type, the state of that block can change, and all players can see the change live.
Persistent inventory views
Inventory views can be reused for persistent operations.
Example: A /persistent command that opens a player’s own, persistent, stash.
// A map to store all inventory views in.
private static final Map<Player, InventoryView> VIEWS = new HashMap<>();
public static LiteralCommandNode<CommandSourceStack> createCommand() {
return Commands.literal("persistent")
.executes(ctx -> {
if (!(ctx.getSource().getExecutor() instanceof Player player)) {
return 0;
}
// First, attempt to get a stored view.
InventoryView view = VIEWS.get(player);
// If there is no view currently stored, create it.
if (view == null) {
view = MenuType.GENERIC_9X6.builder()
.title(Component.text(player.getName() + "'s stash", NamedTextColor.DARK_RED))
.build(player);
// And finally store it in the map.
VIEWS.put(player, view);
}
// As the inventory view is directly bound to the player, we do not have
// to reassign the player and can just open it.
view.open();
return Command.SINGLE_SUCCESS;
})
.build();
}
@EventHandler
void onPlayerQuit(PlayerQuitEvent event) {
InventoryView view = VIEWS.remove(event.getPlayer());
if (view != null) {
Inventory topInventory = view.getTopInventory();
// Save the contents of the inventory to a file or database.
}
}
On the quit event:
- Remove the player entry from the map.
- Store the top inventory somewhere so it persists across server restarts.
Datapack Discovery
Experimental
The Datapack Discovery API is experimental and may change in the future.
Overview
The Datapack Discovery API, part of the Lifecycle API, allows developers to modify core server aspects, specifically datapacks. This allows including datapacks directly within plugin JAR files.
Note: This feature requires understanding of the Lifecycle API, datapack format, and the use of a paper-plugin.yml file.
The Datapack Discovery Lifecycle Event
The LifecycleEvents.DATAPACK_DISCOVERY lifecycle event allows adding, checking for, and removing datapacks about to be loaded by the server.
Tip: Code examples are assumed to be inside a PluginBootstrap's bootstrap(BootstrapContext context) method and within an event handler.
@NullMarked
public class CustomPluginBootstrap implements PluginBootstrap {
@Override
public void bootstrap(BootstrapContext context) {
context.getLifecycleManager().registerEventHandler(LifecycleEvents.DATAPACK_DISCOVERY.newHandler(
event -> {
// All code is contained here.
}
));
}
}
Retrieving All Currently Discovered Datapacks
Use DatapackRegistrar#getDiscoveredPacks() to retrieve discovered datapacks.
context.getLogger().info("The following datapacks were found: {}",
String.join(", ", event.registrar().getDiscoveredPacks().keySet()));
Output example:
[00:26:12 INFO]: [PaperDocsTestProject] The following datapacks were found: file/bukkit, minecart_improvements, paper, redstone_experiments, trade_rebalance, vanilla
Removing Discovered Datapacks
Use DatapackRegistrar#removeDiscoveredPack(String name) to prevent datapacks from being discovered.
final Set<String> datapacksToRemove = Set.of("minecart_improvements", "redstone_experiments", "trade_rebalance");
datapacksToRemove.forEach(datapack -> event.registrar().removeDiscoveredPack(datapack));
context.getLogger().info("The following datapacks were found: {}",
String.join(", ", event.registrar().getDiscoveredPacks().keySet()));
Output example:
[00:35:39 INFO]: [PaperDocsTestProject] The following datapacks were found: file/bukkit, paper, vanilla
Registering Custom Datapacks
The primary use case is adding plugin-included datapacks. You must include the datapack's source files (not the zip) in the plugin's JAR file, typically under src/main/resources.
Tip: If you don't have a datapack, check out Vanilla Tweaks for examples.
Including the Datapack in Your Plugin
Add the datapack to your plugin's src/main/resources folder, with at least one extra folder level. For example, resources/custom_datapack.
Folder structure:
src/main/resources
custom_datapack
pack.mcmeta
data/
...
Build your plugin and verify that there is a custom_datapack folder in the root of your plugin's JAR file.
Discovering the Datapack
Call DatapackRegistrar#discoverPack(URI uri, String id). The URI should point to your datapack’s folder in your JAR. Use getClass().getResource("/custom_datapack").toURI() to get the URI.
Important: The preceding slash is crucial.
The ID can be set to whatever you want to identify your datapack with. The final name of the loaded pack will be <YourPluginName>/<id>.
try {
URI uri = Objects.requireNonNull(getClass().getResource("/custom_datapack")).toURI();
event.registrar().discoverPack(uri, "provided");
} catch (URISyntaxException | IOException e) {
throw new RuntimeException(e);
}
Verifying that the Datapack Loaded Correctly
Verify by executing the command /datapack list enabled.
Alternatively, check the loaded status during plugin execution in the onLoad method:
public final class CustomJavaPlugin extends JavaPlugin {
@Override
public void onLoad() {
Datapack pack = this.getServer().getDatapackManager().getPack(getPluginMeta().getName() + "/provided");
if (pack != null) {
if (pack.isEnabled()) {
this.getLogger().info("The datapack loaded successfully!");
} else {
this.getLogger().warn("The datapack failed to load.");
}
}
}
}
Example console output:
[01:10:12 INFO]: [PaperDocsTestProject] Loading server plugin PaperDocsTestProject v1.0-DEV
[01:10:12 INFO]: [PaperDocsTestProject] The datapack loaded successfully!
Persistent Data Container (PDC)
Overview
The Persistent Data Container (PDC) is a way to store custom data on a variety of objects, including items, entities, and block entities.
Supported Classes:
ItemStackChunkWorldEntityTileStateStructureGeneratedStructureRaidOfflinePlayerItemMeta
What is it used for?
The PDC provides a reliable and performant way to store arbitrary data on objects, replacing older methods like:
- NBT tags: Requires reflection and is unreliable long-term.
- Lore and display names: Prone to collisions and slow to access.
The PDC doesn't rely on server internals and handles the data lifecycle automatically.
Adding data
To store data in the PDC:
- Create a
NamespacedKeyto identify the data. - Get the
PersistentDataContainerfrom the object. - Use the
setmethod to store the data.
NamespacedKey key = new NamespacedKey(pluginInstance, "example-key");
World world = Bukkit.getServer().getWorlds().getFirst();
PersistentDataContainer pdc = world.getPersistentDataContainer();
pdc.set(key, PersistentDataType.STRING, "I love tacos!");
ItemStack requires using its builder-style consumer:
NamespacedKey key = ...;
ItemStack item = ItemStack.of(Material.DIAMOND);
item.editPersistentDataContainer(pdc -> {
pdc.set(key, PersistentDataType.STRING, "I love tacos!");
});
Note: ItemStack#editPersistentDataContainer() is available in 1.21.4+. For older versions, modify the ItemMeta instead. For 1.16.5+, there’s the ItemStack#editMeta() method.
Note: Reuse NamespacedKey objects whenever possible. Construct them with either a Plugin instance and a String identifier, or a String namespace and a String identifier.
Getting data
To retrieve data from the PDC:
- Know the
NamespacedKeyandPersistentDataTypeof the data. - Use the
getorgetOrDefaultmethod to retrieve the data.
NamespacedKey key = ...;
World world = ...;
PersistentDataContainer pdc = world.getPersistentDataContainer();
String value = pdc.getOrDefault(key, PersistentDataType.STRING, "<null>");
player.sendPlainMessage(value);
Data types
The PDC supports a wide range of data types:
- Byte
- Byte Array
- Double
- Float
- Integer
- Integer Array
- Long
- Long Array
- Short
- String
- Boolean
- Tag Containers: Nested PDCs.
PersistentDataContainer container = ...;
PersistentDataContainer newContainer = container.getAdapterContext().newPersistentDataContainer();
- Lists: Lists of data that can be stored via another persistent data type.
container.set(key, PersistentDataType.LIST.listTypeFrom(PersistentDataType.STRING), List.of("a", "list", "of", "strings"));
container.set(key, PersistentDataType.LIST.strings(), List.of("a", "list", "of", "strings"));
List<String> strings = container.get(key, PersistentDataType.LIST.strings());
# Persistent Data Container (PDC)
The Persistent Data Container (PDC) is a way to store custom data on various objects like items, entities, and block entities.
**Supported Classes:**
* `ItemStack`
* `Chunk`
* `World`
* `Entity`
* `TileState`
* `Structure`
* `GeneratedStructure`
* `Raid`
* `OfflinePlayer`
* `ItemMeta`
## What is it used for?
* **Reliable Storage:** Provides a reliable and performant way to store arbitrary data.
* **No Server Internals:** Doesn't rely on accessing server internals, reducing breakage on future versions.
* **Lifecycle Management:** Removes the need to manually track data lifecycle; PDC data is saved when the entity unloads.
* **Replaces Older Methods:** A better alternative to NBT tags (requires reflection, unreliable) and lore/display names (prone to collisions, slow access).
## Adding data
To store data in the PDC, you need:
* `NamespacedKey`: To identify the data.
* `PersistentDataContainer`: The object to store data on.
* The data itself.
```java
NamespacedKey key = new NamespacedKey(pluginInstance, "example-key"); // Create a NamespacedKey
World world = Bukkit.getServer().getWorlds().getFirst();
PersistentDataContainer pdc = world.getPersistentDataContainer();
pdc.set(key, PersistentDataType.STRING, "I love tacos!");
For ItemStack (1.21.4+):
NamespacedKey key = ...;
ItemStack item = ItemStack.of(Material.DIAMOND);
item.editPersistentDataContainer(pdc -> {
pdc.set(key, PersistentDataType.STRING, "I love tacos!");
});
Important Notes:
ItemStack#editPersistentDataContainer()is available in 1.21.4+. For older versions, useItemMeta.- For 1.16.5+,
ItemStack#editMeta()is available. - Reuse
NamespacedKeyobjects. They can be constructed with aPlugininstance and aStringidentifier, or aStringnamespace and aStringidentifier. The first option is preferred for automatic namespacing.
Getting data
To retrieve data:
- Know the
NamespacedKey. - Know the
PersistentDataType.
NamespacedKey key = ...; // Use the same key as the adding-data example
World world = ...; // Use the same world as the adding-data example
PersistentDataContainer pdc = world.getPersistentDataContainer();
String value = pdc.getOrDefault(key, PersistentDataType.STRING, "<null>"); // Utilize the data from the PDC
player.sendPlainMessage(value); // Do something with the value
Use getOrDefault when needing non-null values, especially for Adventure's Component.text(String).
Data types
The PDC supports:
-
Byte,Byte Array -
Double -
Float -
Integer,Integer Array -
Long,Long Array -
Short -
String -
Boolean -
Tag Containers: Nested PDCs.
PersistentDataContainer container = ...; // Get an existing container PersistentDataContainer newContainer = container.getAdapterContext().newPersistentDataContainer(); // Create a new container -
Lists: Lists of data that can be stored via another persistent data type.
// Storing a list of strings in a container by verbosely creating // a list data type wrapping the string data type. container.set(key, PersistentDataType.LIST.listTypeFrom(PersistentDataType.STRING), List.of("a", "list", "of", "strings")); // Storing a list of strings in a container by using the API // provided pre-definitions of commonly used list types. container.set(key, PersistentDataType.LIST.strings(), List.of("a", "list", "of", "strings")); // Retrieving a list of strings from the container. List<String> strings = container.get(key, PersistentDataType.LIST.strings());
Boolean
The Boolean PDC type exists for convenience; you cannot make more complex types distill to a Boolean.
Custom data types
Implement your own PersistentDataType for complex data. The PersistentDataType handles "deconstructing" a complex data type into natively supported types and vice versa.
Example (UUID):
@NullMarked
public class UUIDDataType implements PersistentDataType<byte[], UUID> {
public static final UUIDDataType INSTANCE = new UUIDDataType();
private UUIDDataType() {} // Singleton
@Override
public Class<byte[]> getPrimitiveType() {
return byte[].class;
}
@Override
public Class<UUID> getComplexType() {
return UUID.class;
}
@Override
public byte[] toPrimitive(UUID complex, PersistentDataAdapterContext context) {
ByteBuffer bb = ByteBuffer.allocate(Long.BYTES * 2);
bb.putLong(complex.getMostSignificantBits());
bb.putLong(complex.getLeastSignificantBits());
return bb.array();
}
@Override
public UUID fromPrimitive(byte[] primitive, PersistentDataAdapterContext context) {
ByteBuffer bb = ByteBuffer.wrap(primitive);
long firstLong = bb.getLong();
long secondLong = bb.getLong();
return new UUID(firstLong, secondLong);
}
}
Usage:
container.set(key, UUIDDataType.INSTANCE, uuid);
Read-only containers
ItemStack and OfflinePlayer provide read-only views of their PDC. OfflinePlayer cannot be modified due to disk read operations. Mutable objects generally need to be re-saved, so read-only "views" are preferred.
NamespacedKey key = ...;
ItemStack item = ...;
PersistentDataContainerView pdcView = item.getPersistentDataContainer();
String value = pdcView.getOrDefault(key, PersistentDataType.STRING, "<null>");
player.sendPlainMessage(value);
Note: PDC-view support for ItemStack was introduced in 1.21.1. Use ItemMeta for older versions.
Storing on different objects
Caution: Data is not copied across holders automatically. Manual copying is required when 'moving' between PersistentDataHolders. Placing an ItemStack as a Block (with a TileState) does not copy PDC data.
Objects that implement PersistentDataHolder have a PDC fetched with PersistentDataHolder#getPersistentDataContainer().
ItemStack
- Prior to 1.21.1, the PDC was accessed via
ItemMeta(overhead). - 1.21.1+ exposes a read-only view via
ItemStack#getPersistentDataContainer(). - 1.21.4+ simplifies edits using
ItemStack#editPersistentDataContainer(java.util.function.Consumer).
ItemStack itemStack = ...;
itemStack.editPersistentDataContainer(pdc -> {
pdc.set(key, PersistentDataType.STRING, "I love tacos!");
});
Chunk
Chunk#getPersistentDataContainer()
World
World#getPersistentDataContainer()
Entity
Entity#getPersistentDataContainer()
TileState
Requires casting the block's state to a TileState extension (only works for blocks with block entities).
Block block = ...;
if (block.getState() instanceof Chest chest) {
chest.getPersistentDataContainer().set(key, PersistentDataType.STRING, "I love tacos!");
chest.update();
}
Structure
Structure#getPersistentDataContainer()
GeneratedStructure
GeneratedStructure#getPersistentDataContainer()
Raid
Raid#getPersistentDataContainer()
OfflinePlayer
OfflinePlayer exposes a read-only PDC via OfflinePlayer#getPersistentDataContainer().
ItemMeta
ItemMeta#getPersistentDataContainer()
Particles
This guide explains how to spawn different types of particles. If a particle isn't mentioned, it likely has no special behavior.
Methods for Spawning Particles:
-
ParticleBuilder (Preferred): Reusable, offers improved readability, and includes the
receivers()method for greater control over receivers.Particle.NOTE.builder() .location(someLocation) .offset(2, 0.2, 2) .count(14) .receivers(32, true) .spawn();ParticleBuilder.receivers(32, true)selects all players within 32 blocks (sphere).falseselects players in a cube.
-
spawnParticle() Methods:
World.spawnParticle(): Spawns the particle for all players.Player.spawnParticle(): Spawns the particle only for the player.
count argument behavior
When spawning particles, the Minecraft client behaves differently based on the count argument:
count = 0: A singular particle spawns at the provided location without modification. The offset values are multiplied by theextraargument.count > 0:countnumber of particles are spawned. New offset values are generated using a Gaussian (normal) distribution, multiplied by theextraargument.
Directional particles
These particles have an initial velocity when spawned. Effective speed varies between particles.
Particle.FLAME.builder()
.location(someLocation)
.offset(0.5, 0.5, 0.5)
.count(8)
.extra(0)
.receivers(32, true)
.spawn();
// or
someWorld.spawnParticle(Particle.FLAME, someLocation, 8, 0.5, 0.5, 0.5, 0);
Caution: Leaving the extra parameter unset will default it to 1, likely resulting in unexpected behavior.
Random direction
Setting the count parameter to anything positive will yield a random direction for the velocity.
Particle.CRIT.builder()
.location(someLocation)
.count(6)
.extra(0.6)
.receivers(32, true)
.spawn();
// or
someWorld.spawnParticle(Particle.CRIT, someLocation, 6, 0, 0, 0, 0.6);
Specified direction
To specify the velocity's direction, set the count argument to 0 and use the offset arguments as the direction vector.
ParticleBuilder particleBuilder = Particle.CAMPFIRE_SIGNAL_SMOKE.builder()
.location(someLocation)
.offset(0, 1, 0)
.count(0)
.extra(0.1);
Bukkit.getScheduler().runTaskTimer(plugin, () -> particleBuilder.receivers(32, true).spawn(), 0, 4);
// or
Bukkit.getScheduler().runTaskTimer(plugin, () -> someWorld.spawnParticle(Particle.CAMPFIRE_SIGNAL_SMOKE, someLocation, 0, 0, 1, 0, 0.1), 0, 4);
To make the smoke go down:
ParticleBuilder particleBuilder = Particle.CAMPFIRE_SIGNAL_SMOKE.builder()
.location(someLocation)
.offset(0, -1, 0)
.count(0)
.extra(0.1);
Bukkit.getScheduler().runTaskTimer(plugin, () -> particleBuilder.receivers(32, true).spawn(), 0, 4);
// or
Bukkit.getScheduler().runTaskTimer(plugin, () -> someWorld.spawnParticle(Particle.CAMPFIRE_SIGNAL_SMOKE, someLocation, 0, 0, -1, 0, 0.1), 0, 4);
List of directional particles
BLOCKBUBBLEBUBBLE_COLUMN_UPBUBBLE_POPCAMPFIRE_COSY_SMOKECAMPFIRE_SIGNAL_SMOKECLOUDCRITDAMAGE_INDICATORDRAGON_BREATHDUSTDUST_COLOR_TRANSITIONDUST_PLUMEELECTRIC_SPARKENCHANTED_HITEND_RODFIREWORKFISHINGFLAMEFLASHGLOW_SQUID_INKITEMLARGE_SMOKEPOOFREVERSE_PORTALSCRAPESCULK_CHARGESCULK_CHARGE_POPSCULK_SOULSMALL_FLAMESMOKESNEEZESNOWFLAKESOULSOUL_FIRE_FLAMESPITSQUID_INKTOTEM_OF_UNDYINGTRIAL_SPAWNER_DETECTIONTRIAL_SPAWNER_DETECTION_OMINOUSWAX_OFFWAX_ONWHITE_SMOKE
Colored particles
These particles can be colored by passing a Color object as the data argument.
Particle.ENTITY_EFFECT.builder()
.location(someLocation)
.offset(1, 1, 1)
.count(10)
.data(Color.fromARGB(200, 255, 128, 0))
.receivers(32, true)
.spawn();
// or
someWorld.spawnParticle(Particle.ENTITY_EFFECT, someLocation, 10, 1, 1, 1, Color.fromARGB(200, 255, 128, 0));
- Only
ENTITY_EFFECTsupports the alpha channel for translucent particles. FLASHandTINTED_LEAVESignore the alpha channel.
Dust particles
Vanilla uses the dust particle for redstone particles. They can have a custom color by passing Particle.DustOptions as data.
- Scale factor must be in the range of
0.01to4.0.
ParticleBuilder particleBuilder = Particle.DUST.builder().color(Color.BLUE, 2.0f);
for (double i = -1.0; i <= 1.0; i += 0.25) {
particleBuilder.location(someLocation.clone().add(0, i, 0)).receivers(32, true).spawn();
}
// or
for (double i = -1.0; i <= 1.0; i += 0.25) {
someWorld.spawnParticle(Particle.DUST, someLocation.clone().add(0, i, 0), 1, new Particle.DustOptions(Color.BLUE, 2.0f));
}
- Adding a size argument controls the dust particle's lifetime (in ticks). Default is a random integer between 8 and 40, multiplied by the scale (minimum of 1).
Dust transition particles
Dust transition particles work like dust particles, but transition their color from one to another using Particle.DustTransition.
Particle.DUST_COLOR_TRANSITION.builder()
.location(someLocation)
.offset(0.5, 0, 0)
.count(3)
.colorTransition(Color.RED, Color.BLUE)
.receivers(32, true)
.spawn();
// or
someWorld.spawnParticle(Particle.DUST_COLOR_TRANSITION, someLocation, 3, 0.5, 0, 0, new Particle.DustTransition(Color.RED, Color.BLUE, 1.0f));
Note particles
Note particles use the offsetX argument to determine the color. offsetY and offsetZ are ignored.
Particle.NOTE.builder()
.location(someLocation)
.offset(0.4f, 0, 0)
.count(0)
.receivers(32, true)
.spawn();
// or
someWorld.spawnParticle(Particle.NOTE, someLocation, 0, 0.4f, 0, 0);
Note particle color picker
offsetX values between -1.0 and 1.0 determine the color. Values outside this range repeat the color pattern.
offsetX = 0
To achieve the Vanilla note particle colors, set the offsetX to a fraction of 24.
Particle.NOTE.builder()
.location(someLocation)
.offset(2.0f / 24.0f, 0, 0)
.count(0)
.receivers(32, true)
.spawn();
// or
someWorld.spawnParticle(Particle.NOTE, someLocation, 0, 2.0f / 24.0f, 0, 0);
Trail particles
Trail particles require you to pass a Particle.Trail object as data.
Particle.TRAIL.builder()
.location(someLocation)
.offset(1, 1, 1)
.count(8)
.data(new Particle.Trail(someLocation.clone().add(-4, 0, 4), Color.YELLOW, 40))
.receivers(32, true)
.spawn();
// or
someWorld.spawnParticle(Particle.TRAIL, someLocation, 8, 1, 1, 1, new Particle.Trail(someLocation.clone().add(-4, 0, 4), Color.YELLOW, 40));
Converging particles
Converge to a single point (location). Offset arguments determine the relative spawn location.
Particle.ENCHANT.builder()
.location(someLocation)
.offset(-2, 0, 2)
.count(0)
.receivers(32, true)
.spawn();
// or
someWorld.spawnParticle(Particle.ENCHANT, someLocation, 0, -2, 0, 2);
- Curving:
ENCHANT,NAUTILUS,PORTAL,VAULT_CONNECTION - Straight:
OMINOUS_SPAWNING
List of converging particles
ENCHANTNAUTILUSOMINOUS_SPAWNINGPORTALVAULT_CONNECTION
Material particles
BlockData
Spawn particles that require BlockData by passing BlockData as its data argument.
Particle.BLOCK_CRUMBLE.builder()
.location(someLocation)
.count(4)
.data(BlockType.GLOWSTONE.createBlockData())
.receivers(32, true)
.spawn();
// or
someWorld.spawnParticle(Particle.BLOCK_CRUMBLE, someLocation, 4, BlockType.GLOWSTONE.createBlockData());
- Use
BlockType.createBlockData().Material.createBlockData()orBukkit.createBlockData(Material)are legacy. BLOCKis a directional particle; velocity matters.
ItemStack
Spawn particles that require an ItemStack by passing an ItemStack as its data argument.
Particle.ITEM.builder()
.location(someLocation)
.count(4)
.data(ItemStack.of(Material.DIAMOND_PICKAXE))
.receivers(32, true)
.spawn();
// or
someWorld.spawnParticle(Particle.ITEM, someLocation, 4, ItemStack.of(Material.DIAMOND_PICKAXE));
- Use
ItemStack.of(Material).new ItemStack(Material)is legacy. ITEMis a directional particle.
Sculk particles
Sculk charge
SCULK_CHARGE takes a float as its data argument, representing the particle's "roll" in radians.
Particle.SCULK_CHARGE.builder()
.location(someLocation)
.data((float) Math.toRadians(45))
.extra(0)
.receivers(32, true)
.spawn();
// or
someWorld.spawnParticle(Particle.SCULK_CHARGE, someLocation, 1, 0, 0, 0, 0, (float) Math.toRadians(45));
SCULK_CHARGEis a directional particle.
Shriek
SHRIEK takes an integer as its data argument, setting the delay (in ticks) before the particle spawns.
Particle.SHRIEK.builder()
.location(someLocation)
.data(20)
.receivers(32, true)
.spawn();
// or
someWorld.spawnParticle(Particle.SHRIEK, someLocation, 1, 20);
Vibration
Vibration particles require a Vibration object as data, with a Vibration.Destination (either BlockDestination or EntityDestination) and a travel time in ticks.
Particle.VIBRATION.builder()
.location(someLocation)
.data(new Vibration(new Vibration.Destination.BlockDestination(otherLocation), 40))
.receivers(32, true)
.spawn();
// or
someWorld.spawnParticle(Particle.VIBRATION, someLocation, 1, new Vibration(new Vibration.Destination.BlockDestination(otherLocation), 40));
Rising particles
These particles use offsetY as the particle's y-axis velocity. If offsetX AND offsetZ are 0, the particle will have almost no x or z-axis velocity.
Caution: EFFECT and INSTANT_EFFECT are powered particles and use Particle.Spell as their data. Due to the low vertical velocity range [-0.056, 0.056] and powered particle calculations, the resulting vertical velocity will always be the opposite of the power value's sign.
Particle.GLOW.builder()
.location(someLocation)
.count(0)
.offset(0, 2, 0)
.receivers(32, true)
.spawn();
// or
someWorld.spawnParticle(Particle.GLOW, someLocation, 0, 0, 2, 0);
These particles rise up, meaning that the initial velocity will be used only briefly, and the particle will start to travel up after a short time. Therefore, negative vertical velocity will only stop the particle from rising temporarily, while a positive vertical velocity will make the particle rise immediately.
List of rising particles
EFFECTENTITY_EFFECTGLOWINFESTEDINSTANT_EFFECTRAID_OMENTRIAL_OMENWITCH
Scalable particles
These particles can be scaled with offsetX, while offsetY and offsetZ are ignored.
- If the final calculated scale is negative, the particle will appear mirrored.
Sweep attack particles
SWEEP_ATTACK particle's scale is calculated as 1.0 - offsetX * 0.5.
ParticleBuilder sweepAttackParticleBuilder = Particle.SWEEP_ATTACK.builder()
.location(someLocation)
.count(0)
.receivers(32, true);
sweepAttackParticleBuilder.spawn();
Bukkit.getScheduler().runTaskLater(plugin, () -> sweepAttackParticleBuilder.offset(-2.0, 0, 0).spawn(), 10);
// or
someWorld.spawnParticle(Particle.SWEEP_ATTACK, someLocation, 0);
Bukkit.getScheduler().runTaskLater(plugin, () -> someWorld.spawnParticle(Particle.SWEEP_ATTACK, someLocation, 0, -2.0, 0, 0), 10);
Explosion particles
EXPLOSION particle's scale is calculated as 2.0 * (1.0 - offsetX * 0.5).
ParticleBuilder explosionParticleBuilder = Particle.EXPLOSION.builder()
.location(someLocation)
.offset(1, 0, 0)
.count(0)
.receivers(32, true);
explosionParticleBuilder.spawn();
Bukkit.getScheduler().runTaskLater(plugin, () -> explosionParticleBuilder.offset(-2.0, 0, 0).spawn(), 10);
// or
someWorld.spawnParticle(Particle.EXPLOSION, someLocation, 0, 1, 0, 0);
Bukkit.getScheduler().runTaskLater(plugin, () -> someWorld.spawnParticle(Particle.EXPLOSION, someLocation, 0, -2.0, 0, 0), 10);
Miscellaneous behaviors
Particles with unique spawning behaviors.
Angry villager particles
ANGRY_VILLAGER always spawns 0.5 higher (y-axis) than the supplied location.
Cloud particles
CLOUD and SNEEZE move towards the player's y level if within two blocks distance. Their vertical velocity will be greatly reduced upon reaching the player's y level. If the player is moving vertically, the particles will attempt to match the player's vertical velocity.
Damage indicator particles
DAMAGE_INDICATOR adds 1.0 to the provided offsetY.
Dust pillar particles
DUST_PILLAR uses offsetY for the y-axis velocity, while offsetX and offsetZ are ignored.
Dust plume particles
DUST_PLUME adds 0.15 to the provided offsetY.
Firefly particles
FIREFLY uses offsetY as the particle's initial y-axis velocity, but there is a 50% chance for the offsetY's sign to be inverted.
Powered particles
The powered particles multiply the particle's velocity vector by the supplied argument. The y component of the vector is calculated as (verticalVelocity - 0.1) * power + 0.1.
List of powered particles
EFFECTINSTANT_EFFECTDRAGON_BREATH
Splash particles
SPLASH uses the offsetX and offsetZ arguments to determine the particle's velocity vector if:
offsetYis0- Either
offsetXoroffsetZare not0
Plugin configuration
Configuration files allow users to change certain behavior and functionality of plugins.
Format
By default, plugins use a YAML configuration format (.yml file). Other formats (JSON, TOML) are not natively supported.
YAML uses a tree-like key: value pair structure, as seen in plugin.yml.
Example:
root:
one-key: 10
another-key: David
Accessing indented values uses dots (.). The key for "David" is root.another-key.
Creating a config.yml
Place a config.yml file in the resources directory to specify default settings.
example-plugin/
└── src/
└── main/
└── java/
└── resources/
├── config.yml
└── plugin.yml
Save this resource to the plugin's data directory during initialization so users can edit it.
public class TestPlugin extends JavaPlugin {
@Override
public void onEnable() {
saveResource("config.yml", /* replace */ false);
// You can also use this for configuration files:
saveDefaultConfig(); // Saves config.yml if it doesn't exist
// getConfig()...
}
}
- The
replaceparameter specifies whether to overwrite an existing file (if set to true, the configuration will be overwritten on every call).
Getting and setting data
Fetch the plugin's FileConfiguration with JavaPlugin#getConfig() after saving. Use #get...(key) and #set(key, value) to fetch and set data.
Basic data types are supported by YAML (e.g., #getString(key), #getBoolean(key)). More complex Bukkit data types (e.g., ItemStack, Location, Vector) are also supported.
Example (Loading a Location):
public class TestPlugin extends JavaPlugin {
public void teleportPlayer(Player player) {
Location to = getConfig().getLocation("target_location");
player.teleport(to);
}
}
These complex types implement ConfigurationSerializable. You can use this for custom classes.
public class TeleportOptions implements ConfigurationSerializable {
private int chunkX;
private int chunkZ;
private String name;
public TeleportOptions(int chunkX, int chunkZ, String name) {
// Set the values
}
public Map<String, Object> serialize() {
Map<String, Object> data = new HashMap<>();
data.put("chunk-x", this.chunkX);
data.put("chunk-z", this.chunkZ);
data.put("name", this.name);
return data;
}
public static TeleportOptions deserialize(Map<String, Object> args) {
return new TeleportOptions((int) args.get("chunk-x"), (int) args.get("chunk-z"), (String) args.get("name"));
}
}
To register the custom class:
ConfigurationSerialization.registerClass(TeleportOptions.class);
Caution: If you do not call ConfigurationSerialization#registerClass(Class) with Paper plugins, you will not be able to load nor save your custom classes.
Saving Configs:
Call FileConfiguration#save(File/String) to persist changes to disk.
Custom configuration files
Split configurations across multiple files using the Bukkit FileConfiguration API.
File file = new File(plugin.getDataFolder(), "items.yml");
YamlConfiguration config = YamlConfiguration.loadConfiguration(file);
// Work with config here
config.save(file);
This example reads items.yml from the plugin's data directory. The file must exist.
Blocking I/O:
Loading and saving files on the main thread will slow down your server. load and save operations should be executed asynchronously.
Configurate
Configurate is a third-party library for working with configurations, maintained by the Sponge project. It's used internally by Paper and offers features the FileConfiguration API lacks. More information can be found here.
plugin.yml
The plugin.yml file is the main configuration file for your plugin, containing information such as name, version, description, dependencies, permissions, and commands. It's located in the resources directory.
example-plugin/
├── build.gradle.kts
├── settings.gradle.kts
└── src/
└── main/
└── java/
└── resources/
└── plugin.yml
Example
name: ExamplePlugin
version: 1.0.0
main: io.papermc.testplugin.ExamplePlugin
description: An example plugin
author: PaperMC
website: https://papermc.io
api-version: '1.21.10'
Fields
* indicates a required field.
name*
The name of your plugin. Displayed in the plugin list and log messages (overridden by prefix if set).
name: ExamplePlugin
version*
The current version of the plugin. Shown in plugin info messages and server logs.
version: 1.0.0
main*
The main class of your plugin. Extends JavaPlugin and is the entry point. Package path and class name of your main class.
main: io.papermc.testplugin.ExamplePlugin
description
A short description of your plugin. Shown in plugin info commands.
description: An example plugin
author / authors
The author(s) of the plugin. Can be a single author or a list. Shown in plugin info commands.
author: PaperMC
authors: [PaperMC, SpigotMC, Bukkit]
contributors
The contributors to the plugin that aren't the managing author(s). Shown in plugin info commands.
contributors: [PaperMC, SpigotMC, Bukkit]
website
The website of the plugin (GitHub repository, plugin page). Shown in plugin info commands.
website: https://papermc.io
api-version
The Paper API version your plugin uses. Doesn’t include minor version until 1.20.5. From 1.20.5 and onward, a minor version is supported. Servers with lower versions refuse to load the plugin. Valid versions are 1.13 - 1.21.10.
api-version: '1.21.10'
- If not specified, the plugin is loaded as legacy, with a console warning.
load
Tells the server when to load the plugin: STARTUP or POSTWORLD. Defaults to POSTWORLD if not specified.
load: STARTUP
prefix
The prefix of the plugin. Displayed in the log instead of the plugin name.
prefix: EpicPaperMCHypePlugin
libraries
A list of libraries your plugin depends on. Downloaded from Maven Central and added to the classpath, removing the need to shade and relocate.
libraries:
- com.google.guava:guava:30.1.1-jre
- com.google.code.gson:gson:2.8.6
- The central repository is configurable using the `PAPER_DEFAULT_CENTRAL_
# PaperMC Knowledge Base
## Project Setup
### Overview
This guide focuses on setting up a Paper plugin development environment using IntelliJ IDEA and Gradle. While tailored for IntelliJ, the principles apply to other IDEs with minor adjustments. Gradle is the preferred build system, though alternatives like Maven can be adapted.
### Creating a New Project
1. **Open IntelliJ IDEA** and select "New Project".
2. **Choose Gradle - Kotlin DSL** as the project type.
3. Click **Create**. This opens the `build.gradle.kts` file for dependency management.
### Adding Paper as a Dependency
Add the Paper repository and dependency to your `build.gradle.kts` (Kotlin), `build.gradle` (Groovy), or `pom.xml` (Maven) file.
#### Gradle (Kotlin)
```kotlin
repositories {
maven {
name = "papermc"
url = uri("https://repo.papermc.io/repository/maven-public/")
}
}
dependencies {
compileOnly("io.papermc.paper:paper-api:1.21.10-R0.1-SNAPSHOT")
}
java {
toolchain.languageVersion.set(JavaLanguageVersion.of(21))
}
Gradle (Groovy)
repositories {
maven {
name = 'papermc'
url = 'https://repo.papermc.io/repository/maven-public/'
}
}
dependencies {
compileOnly 'io.papermc.paper:paper-api:1.21.10-R0.1-SNAPSHOT'
}
Maven
<project>
<repositories>
<repository>
<id>papermc</id>
<url>https://repo.papermc.io/repository/maven-public/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
<version>1.21.10-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
Setting up the src Directory
- Create a directory named
src. - Inside
src, create a directory namedmain. - Inside
main, create two directories:javaandresources.
example-plugin/
├── build.gradle.kts
├── settings.gradle.kts
└── src/
└── main/
├── java/
└── resources/
Setting up the java Directory
Place your Java source files in the java directory. Organize code into packages.
- Create packages to organize your code (e.g.,
io.papermc.testplugin). - Create Java classes within these packages (e.g.,
ExamplePlugin.java).
example-plugin/
├── build.gradle.kts
├── settings.gradle.kts
└── src/
└── main/
├── java/
│ └── io/
│ └── papermc/
│ └── testplugin/
│ └── ExamplePlugin.java
└── resources/
Packages
Packages are used to organize code. Java packages group related classes. Package names should follow a reverse domain name convention (e.g., io.papermc.testplugin). If you don't have a domain, use your GitHub username (e.g., io.github.yourname).
The main Class
The main class extends JavaPlugin and serves as the entry point for the plugin.
package io.papermc.testplugin;
import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.plugin.java.JavaPlugin;
public class ExamplePlugin extends JavaPlugin implements Listener {
@Override
public void onEnable() {
Bukkit.getPluginManager().registerEvents(this, this);
}
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
event.getPlayer().sendMessage(Component.text("Hello, " + event.getPlayer().getName() + "!"));
}
}
Setting up the resources Directory
Place the plugin.yml file in the resources directory.
Using the Minecraft Development IntelliJ Plugin
An alternative to manual setup is using the Minecraft Development IntelliJ plugin.
-
Install the Plugin: Go to
File > Settings > Plugins, search for "Minecraft Development" in the Marketplace, and install it. Restart IntelliJ. -
Create a New Project: Go to
File > New > Project...and select "Minecraft". -
Provide Project Information:
- Name: Project name.
- Location: Project directory.
- Platform Type: "Plugin".
- Platform: "Paper".
- Minecraft Version: Target Minecraft version.
- Plugin Name: Plugin name.
- Main Class: Main class extending
JavaPlugin. - Optional Settings: Author, website, description.
- Build System: Gradle (recommended) or Maven.
- Paper Manifest: Leave unchecked for now (experimental).
- Group ID: Reverse domain name (e.g.,
io.github.yourname). - Artifact ID: Project name.
- Version:
1.0-SNAPSHOT. - JDK: Java 21 or higher.
-
Click Create.
Plugin Remapping
Paper uses a Mojang-mapped runtime since 1.20.5. Spigot/Bukkit plugins are assumed to be Spigot-mapped and will be remapped on first load.
Mojang Mappings
To indicate your plugin is Mojang-mapped, add the following to your build script:
Gradle (Kotlin)
tasks.jar {
manifest {
attributes["paperweight-mappings-namespace"] = "mojang"
}
}
// if you have shadowJar configured
tasks.shadowJar {
manifest {
attributes["paperweight-mappings-namespace"] = "mojang"
}
}
Gradle (Groovy)
jar {
manifest {
attributes('paperweight-mappings-namespace': 'mojang')
}
}
// if you have shadowJar configured
shadowJar {
manifest {
attributes('paperweight-mappings-namespace': 'mojang')
}
}
Maven
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.4.1</version>
<configuration>
<archive>
<manifestEntries>
<paperweight-mappings-namespace>mojang</paperweight-mappings-namespace>
</manifestEntries>
</archive>
</configuration>
</plugin>
Spigot Mappings
To explicitly indicate your plugin is Spigot-mapped, add the following to your build script:
Gradle (Kotlin)
tasks.jar {
manifest {
attributes["paperweight-mappings-namespace"] = "spigot"
}
}
// if you have shadowJar configured
tasks.shadowJar {
manifest {
attributes["paperweight-mappings-namespace"] = "spigot"
}
}
Gradle (Groovy)
jar {
manifest {
attributes('paperweight-mappings-namespace': 'spigot')
}
}
// if you have shadowJar configured
shadowJar {
manifest {
attributes('paperweight-mappings-namespace': 'spigot')
}
}
Maven
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.4.1</version>
<configuration>
<archive>
<manifestEntries>
<paperweight-mappings-namespace>spigot</paperweight-mappings-namespace>
</manifestEntries>
</archive>
</configuration>
</plugin>
Conclusion
You should now have a project set up with Paper as a dependency. Compile your plugin and run it on a Paper server.
- Run-Task Gradle Plugin: Streamlines testing by automatically downloading and running a Paper server.
- IntelliJ Build Menu: Compile your plugin using the Gradle GUI "Build" menu. The output JAR will be in the
build/libsdirectory.
Reading Stacktraces
Overview
A stacktrace displays the call stack of a thread in Java, showing the execution path leading to a specific point in the program. It's crucial for debugging.
What is a Stacktrace?
In Java, a stacktrace reveals the call stack of a thread, detailing the sequence of method calls that led to the current execution point. Stacktraces are printed when an exception isn't handled correctly and are invaluable for debugging. They pinpoint the exact line causing the error and the preceding calls.
Example
[15:20:42 ERROR]: Could not pass event PluginEnableEvent to TestPlugin v1.0
java.lang.NullPointerException: Cannot invoke "Object.toString()" because "player" is null
at io.papermc.testplugin.TestPlugin.onPluginEnable(TestPlugin.java:23) ~[TestPlugin-1.0-SNAPSHOT.jar:?]
at com.destroystokyo.paper.event.executor.asm.generated.GeneratedEventExecutor1.execute(Unknown Source) ~[?:?]
at org.bukkit.plugin.EventExecutor$2.execute(EventExecutor.java:77) ~[paper-api-1.20.2-R0.1-SNAPSHOT.jar:?]
at co.aikar.timings.TimedEventExecutor.execute(TimedEventExecutor.java:81) ~[paper-api-1.20.2-R0.1-SNAPSHOT.jar:git-Paper-49]
at org.bukkit.plugin.RegisteredListener.callEvent(RegisteredListener.java:70) ~[paper-api-1.20.2-R0.1-SNAPSHOT.jar:?]
at io.papermc.paper.plugin.manager.PaperEventManager.callEvent(PaperEventManager.java:54) ~[paper-1.20.2.jar:git-Paper-49]
at io.papermc.paper.plugin.manager.PaperPluginManagerImpl.callEvent(PaperPluginManagerImpl.java:126) ~[paper-1.20.2.jar:git-Paper-49]
at org.bukkit.plugin.SimplePluginManager.callEvent(SimplePluginManager.java:615) ~[paper-api-1.20.2-R0.1-SNAPSHOT.jar:?]
at io.papermc.paper.plugin.manager.PaperPluginInstanceManager.enablePlugin(PaperPluginInstanceManager.java:200) ~[paper-1.20.2.jar:git-Paper-49]
at io.papermc.paper.plugin.manager.PaperPluginManagerImpl.enablePlugin(PaperPluginManagerImpl.java:104) ~[paper-1.20.2.jar:git-Paper-49]
at org.bukkit.plugin.SimplePluginManager.enablePlugin(SimplePluginManager.java:507) ~[paper-api-1.20.2-R0.1-SNAPSHOT.jar:?]
at org.bukkit.craftbukkit.v1_20_R2.CraftServer.enablePlugin(CraftServer.java:636) ~[paper-1.20.2.jar:git-Paper-49]
at org.bukkit.craftbukkit.v1_20_R2.CraftServer.enablePlugins(CraftServer.java:547) ~[paper-1.20.2.jar:git-Paper-49]
at net.minecraft.server.MinecraftServer.loadWorld0(MinecraftServer.java:636) ~[paper-1.20.2.jar:git-Paper-49]
at net.minecraft.server.MinecraftServer.loadLevel(MinecraftServer.java:435) ~[paper-1.20.2.jar:git-Paper-49]
at net.minecraft.server.dedicated.DedicatedServer.initServer(DedicatedServer.java:308) ~[paper-1.20.2.jar:git-Paper-49]
at net.minecraft.server.MinecraftServer.runServer(MinecraftServer.java:1101) ~[paper-1.20.2.jar:git-Paper-49]
at net.minecraft.server.MinecraftServer.lambda$spin$0(MinecraftServer.java:318) ~[paper-1.20.2.jar:git-Paper-49]
at java.lang.Thread.run(Thread.java:833) ~[?:?]
- Error Context: The error occurred during
PluginEnableEventhandling byTestPlugin. - Exception Cause:
NullPointerExceptiondue to callingtoString()on a null "player" object. - Error Location:
TestPlugin.java:23. - Execution Path: The stacktrace shows the sequence of calls that led to the error, starting from
TestPlugin.onPluginEnableand traversing through Paper/Bukkit internals. You can ignore server internals typically.
Omitted Stacktraces
In JDK 5+, the JVM may omit stacktraces for optimized code, leading to NullPointerExceptions without a stacktrace. To resolve this, use the JVM flag:
java -XX:-OmitStackTraceInFastThrow -jar paper.jar
Particles
Overview
This guide explains how to spawn different types of particles. If a particle isn't mentioned, it likely has no special behavior.
Particles can be spawned in two ways:
ParticleBuilderClass (Preferred): Reusable, readable, and includesreceivers()for controlling receivers.World.spawnParticle()andPlayer.spawnParticle(): Spawn particles for all players or a specific player, respectively.
Example using ParticleBuilder:
Particle.NOTE.builder()
.location(someLocation)
.offset(2, 0.2, 2)
.count(14)
.receivers(32, true)
.spawn();
ParticleBuilder.receivers(32, true) selects players within a 32-block radius (sphere). false would select players within a cube.
count Argument Behavior
count = 0: Spawns a single particle at the provided location without modification. Offset values are multiplied by theextraargument.count > 0: Spawnscountparticles. Offset values are generated using a Gaussian distribution and multiplied by theextraargument.
Directional Particles
These particles have an initial velocity when spawned. Effective speed varies between particles.
Example:
Particle.FLAME.builder()
.location(someLocation)
.offset(0.5, 0.5, 0.5)
.count(8)
.extra(0)
.receivers(32, true)
.spawn();
//or
someWorld.spawnParticle(Particle.FLAME, someLocation, 8, 0.5, 0.5, 0.5, 0);
Leaving extra unset defaults to 1, which may result in unexpected behavior.
Random Direction
Setting count to a positive value yields a random direction for the velocity.
Example:
Particle.CRIT.builder()
.location(someLocation)
.count(6)
.extra(0.6)
.receivers(32, true)
.spawn();
//or
someWorld.spawnParticle(Particle.CRIT, someLocation, 6, 0, 0, 0, 0.6);
Specified Direction
Set count to 0 and use the offset arguments as the direction vector.
Example:
ParticleBuilder particleBuilder = Particle.CAMPFIRE_SIGNAL_SMOKE.builder()
.location(someLocation)
.offset(0, 1, 0)
.count(0)
.extra(0.1);
Bukkit.getScheduler().runTaskTimer(plugin, () -> particleBuilder.receivers(32, true).spawn(), 0, 4);
//or
Bukkit.getScheduler().runTaskTimer(plugin, () -> someWorld.spawnParticle(Particle.CAMPFIRE_SIGNAL_SMOKE, someLocation, 0, 0, 1, 0, 0.1), 0, 4);
List of Directional Particles
- BLOCK
- BUBBLE
- BUBBLE_COLUMN_UP
- BUBBLE_POP
- CAMPFIRE_COSY_SMOKE
- CAMPFIRE_SIGNAL_SMOKE
- CLOUD
- CRIT
- DAMAGE_INDICATOR
- DRAGON_BREATH
- DUST
- DUST_COLOR_TRANSITION
- DUST_PLUME
- ELECTRIC_SPARK
- ENCHANTED_HIT
- END_ROD
- FIREWORK
- FISHING
- FLAME
- FLASH
- GLOW_SQUID_INK
- ITEM
- LARGE_SMOKE
- POOF
- REVERSE_PORTAL
- SCRAPE
- SCULK_CHARGE
- SCULK_CHARGE_POP
- SCULK_SOUL
- SMALL_FLAME
- SMOKE
- SNEEZE
- SNOWFLAKE
- SOUL
- SOUL_FIRE_FLAME
- SPIT
- SQUID_INK
- TOTEM_OF_UNDYING
- TRIAL_SPAWNER_DETECTION
- TRIAL_SPAWNER_DETECTION_OMINOUS
- WAX_OFF
- WAX_ON
- WHITE_SMOKE
Colored Particles
These particles can be colored by passing a Color object as the data argument.
Example:
Particle.ENTITY_EFFECT.builder()
.location(someLocation)
.offset(1, 1, 1)
.count(10)
.data(Color.fromARGB(200, 255, 128, 0))
.receivers(32, true)
.spawn();
//or
someWorld.spawnParticle(Particle.ENTITY_EFFECT, someLocation, 10, 1, 1, 1, Color.fromARGB(200, 255, 128, 0));
Only ENTITY_EFFECT supports the alpha channel for translucency. FLASH and TINTED_LEAVES ignore the alpha channel.
Dust Particles
Vanilla uses dust particles for redstone. Custom colors are set using Particle.DustOptions as data. The scale factor must be between 0.01 and 4.0.
Example:
ParticleBuilder particleBuilder = Particle.DUST.builder().color(Color.BLUE, 2.0f);
for (double i = -1.0; i <= 1.0; i += 0.25) {
particleBuilder.location(someLocation.clone().add(0, i, 0)).receivers(32, true).spawn();
}
//or
for (double i = -1.0; i <= 1.0; i += 0.25) {
someWorld.spawnParticle(Particle.DUST, someLocation.clone().add(0, i, 0), 1, new Particle.DustOptions(Color.BLUE, 2.0f));
}
A size argument controls the dust particle's lifetime in ticks.
Dust Transition Particles
Dust transition particles transition their color. A Particle.DustTransition is used to specify the transition.
Example:
Particle.DUST_COLOR_TRANSITION.builder()
.location(someLocation)
.offset(0.5, 0, 0)
.count(3)
.colorTransition(Color.RED, Color.BLUE)
.receivers(32, true)
.spawn();
//or
someWorld.spawnParticle(Particle.DUST_COLOR_TRANSITION, someLocation, 3, 0.5, 0, 0, new Particle.DustTransition(Color.RED, Color.BLUE, 1.0f));
Note Particles
Note particles use the offsetX argument to determine the color. offsetY and offsetZ are ignored.
Example:
Particle.NOTE.builder()
.location(someLocation)
.offset(0.4f, 0, 0)
.count(0)
.receivers(32, true)
.spawn();
//or
someWorld.spawnParticle(Particle.NOTE, someLocation, 0, 0.4f, 0, 0);
Note Particle Color Picker
offsetX values between -1.0 and 1.0 determine the color. Values outside this range repeat the pattern. For Vanilla note particle colors, set offsetX to a fraction of 24 (e.g., 2.0f / 24.0f).
Example:
Particle.NOTE.builder()
.location(someLocation)
.offset(2.0f / 24.0f, 0, 0)
.count(0)
.receivers(32, true)
.spawn();
//or
someWorld.spawnParticle(Particle.NOTE, someLocation, 0, 2.0f / 24.0f, 0, 0);
Trail Particles
Trail particles require a Particle.Trail object as data.
Example:
Particle.TRAIL.builder()
.location(someLocation)
.offset(1, 1, 1)
.count(8)
.data(new Particle.Trail(someLocation.clone().add(-4, 0, 4), Color.YELLOW, 40))
.receivers(32, true)
.spawn();
//or
someWorld.spawnParticle(Particle.TRAIL, someLocation, 8, 1, 1, 1, new Particle.Trail(someLocation.clone().add(-4, 0, 4), Color.YELLOW, 40));
Converging Particles
These particles converge to a single point. Offset arguments determine the relative spawn location.
Example:
Particle.ENCHANT.builder()
.location(someLocation)
.offset(-2, 0, 2)
.count(0)
.receivers(32, true)
.spawn();
//or
someWorld.spawnParticle(Particle.ENCHANT, someLocation, 0, -2, 0, 2);
- Curving:
ENCHANT,NAUTILUS,PORTAL,VAULT_CONNECTION - Straight:
OMINOUS_SPAWNING
List of Converging Particles
- ENCHANT
- NAUTILUS
- OMINOUS_SPAWNING
- PORTAL
- VAULT_CONNECTION
Material Particles
BlockData
To spawn particles requiring BlockData, use BlockData as the data argument.
Example:
Particle.BLOCK_CRUMBLE.builder()
.location(someLocation)
.count(4)
.data(BlockType.GLOWSTONE.createBlockData())
.receivers(32, true)
.spawn();
//or
someWorld.spawnParticle(Particle.BLOCK_CRUMBLE, someLocation, 4, BlockType.GLOWSTONE.createBlockData());
Using BlockType.createBlockData() is preferred over legacy methods.
The BLOCK particle is a directional particle, so velocity matters.
ItemStack
To spawn particles requiring an ItemStack, use an ItemStack as the data argument.
Example:
Particle.ITEM.builder()
.location(someLocation)
.count(4)
.data(ItemStack.of(Material.DIAMOND_PICKAXE))
.receivers(32, true)
.spawn();
//or
someWorld.spawnParticle(Particle.ITEM, someLocation, 4, ItemStack.of(Material.DIAMOND_PICKAXE));
Using ItemStack.of(Material) is preferred over legacy methods.
The ITEM particle is a directional particle.
Sculk Particles
Sculk Charge
The SCULK_CHARGE particle takes a float as data, representing the particle's roll in radians.
Example:
Particle.SCULK_CHARGE.builder()
.location(someLocation)
.data((float) Math.toRadians(45))
.extra(0)
.receivers(32, true)
.spawn();
//or
someWorld.spawnParticle(Particle.SCULK_CHARGE, someLocation, 1, 0, 0, 0, 0, (float) Math.toRadians(45));
SCULK_CHARGE is a directional particle.
Shriek
The SHRIEK particle takes an integer as data, representing the delay in ticks before the particle spawns.
Example:
Particle.SHRIEK.builder()
.location(someLocation)
.data(20)
.receivers(32, true)
.spawn();
//or
someWorld.spawnParticle(Particle.SHRIEK, someLocation, 1, 20);
Vibration
Vibration particles require a Vibration object as data, specifying a location (Vibration.Destination.BlockDestination) or entity target (Vibration.Destination.EntityDestination). The constructor's second argument is the travel time in ticks.
Example:
Particle.VIBRATION.builder()
.location(someLocation)
.data(new Vibration(new Vibration.Destination.BlockDestination(otherLocation), 40))
.receivers(32, true)
.spawn();
//or
someWorld.spawnParticle(Particle.VIBRATION, someLocation, 1, new Vibration(new Vibration.Destination.BlockDestination(otherLocation), 40));
Rising Particles
These particles use offsetY as the y-axis velocity. Setting offsetX and offsetZ to 0 results in minimal x/z velocity.
Caution: EFFECT and INSTANT_EFFECT are powered particles.
Example:
Particle.GLOW.builder()
.location(someLocation)
.count(0)
.offset(0, 2, 0)
.receivers(32, true)
.spawn();
//or
someWorld.spawnParticle(Particle.GLOW, someLocation, 0, 0, 2, 0);
Initial velocity is used briefly, then the particle rises.
List of Rising Particles
- EFFECT
- ENTITY_EFFECT
- GLOW
- INFESTED
- INSTANT_EFFECT
- RAID_OMEN
- TRIAL_OMEN
- WITCH
Scalable Particles
These particles can be scaled with offsetX. offsetY and offsetZ are ignored.
Sweep Attack Particles
The SWEEP_ATTACK particle's scale is calculated as 1.0 - offsetX * 0.5.
Example:
ParticleBuilder sweepAttackParticleBuilder = Particle.SWEEP_ATTACK.builder().location(someLocation).count(0).receivers(32, true);
sweepAttackParticleBuilder.spawn();
Bukkit.getScheduler().runTaskLater(plugin, () -> sweepAttackParticleBuilder.offset(-2.0, 0, 0).spawn(), 10);
//or
someWorld.spawnParticle(Particle.SWEEP_ATTACK, someLocation, 0);
Bukkit.getScheduler().runTaskLater(plugin, () -> someWorld.spawnParticle(Particle.SWEEP_ATTACK, someLocation, 0, -2.0, 0, 0), 10);
Explosion Particles
The EXPLOSION particle's scale is calculated as 2.0 * (1.0 - offsetX * 0.5).
Example:
ParticleBuilder explosionParticleBuilder = Particle.EXPLOSION.builder().location(someLocation).offset(1, 0, 0).count(0).receivers(32, true);
explosionParticleBuilder.spawn();
Bukkit.getScheduler().runTaskLater(plugin, () -> explosionParticleBuilder.offset(-2.0, 0, 0).spawn(), 10);
//or
someWorld.spawnParticle(Particle.EXPLOSION, someLocation, 0, 1, 0, 0);
Bukkit.getScheduler().runTaskLater(plugin, () -> someWorld.spawnParticle(Particle.EXPLOSION, someLocation, 0, -2.0, 0, 0), 10);
Miscellaneous Behaviors
Angry Villager Particles
The ANGRY_VILLAGER particle spawns 0.5 higher (y-axis) than the supplied location.
Cloud Particles
The CLOUD and SNEEZE particles move towards the player's y level if within two blocks. They attempt to match vertical velocity.
Damage Indicator Particles
The DAMAGE_INDICATOR particle adds 1.0 to the provided offsetY.
Dust Pillar Particles
The DUST_PILLAR particle uses offsetY for the y-axis velocity. offsetX and offsetZ are ignored.
Dust Plume Particles
The DUST_PLUME particle adds 0.15 to the provided offsetY.
Firefly Particles
The FIREFLY particle uses offsetY as the initial y-axis velocity, but there's a 50% chance for the sign to be inverted.
Powered Particles
Powered particles multiply the velocity vector by the supplied argument. The y component is calculated as (verticalVelocity - 0.1) * power + 0.1.
List of Powered Particles
- EFFECT
- INSTANT_EFFECT
- DRAGON_BREATH
Splash Particles
The SPLASH particle uses offsetX and offsetZ to determine the velocity vector if offsetY is 0 and either offsetX or offsetZ are not 0.
plugin.yml
Overview
The plugin.yml file is the primary configuration file for a Paper plugin. It contains metadata about the plugin, its dependencies, permissions, and commands. It resides in the resources directory of the project.
Example
name: ExamplePlugin
version: 1.0.0
main: io.papermc.testplugin.ExamplePlugin
description: An example plugin
author: PaperMC
website: https://papermc.io
api-version: '1.21.10'
Fields
Note: Asterisks () indicate required fields.*
name*
The name of the plugin. Displayed in plugin lists and log messages (unless prefix is set).
name: ExamplePlugin
version*
The plugin's current version. Shown in plugin info messages and server logs.
version: 1.0.0
main*
The main class of the plugin. Extends JavaPlugin and serves as the plugin's entry point. It's the package path and class name.
main: io.papermc.testplugin.ExamplePlugin
description
A brief description of the plugin. Shown in plugin info commands.
description: An example plugin
author / authors
The plugin's author(s). Can be a single author or a list.
author: PaperMC
authors: [PaperMC, SpigotMC, Bukkit]
contributors
Contributors to the plugin who aren't managing authors.
contributors: [PaperMC, SpigotMC, Bukkit]
website
The plugin's website (e.g., GitHub repository or plugin page).
website: https://papermc.io
api-version
The Paper API version the plugin uses. Includes minor version support from 1.20.5 onwards. Servers with lower versions refuse to load the plugin. Valid versions are 1.13 - 1.21.10.
api-version: '1.21.10'
Note: If unspecified, the plugin loads as a legacy plugin with a console warning.
load
Determines when the server loads the plugin: STARTUP or POSTWORLD. Defaults to POSTWORLD.
load: STARTUP
prefix
A prefix displayed in the log instead of the plugin name.
prefix: EpicPaperMCHypePlugin
libraries
A list of libraries the plugin depends on. Downloaded from Maven Central and added to the classpath, eliminating the need for shading and relocation.
libraries:
- com.google.guava:guava:30.1.1-jre
- com.google.code.gson:gson:2.8.6
Note: The central repository is configurable via the PAPER_DEFAULT_CENTRAL_REPOSITORY environment variable and org.bukkit.plugin.java.LibraryLoader.centralURL system property.
permissions
A list of permissions the plugin uses to restrict access to features.
permissions:
permission.node:
description: "This is a permission node"
default: op
children:
permission.node.child: true
another.permission.node:
description: "This is another permission node"
default: notop
- description: Description of the permission node.
- default: Default value (
op,notop,true,false). Defaults todefault-permissionif unspecified, which defaults toop. - children: Inherits the parent permission if set to
true.
default-permission
The default value for permissions without a specified default value (op,
# Reading Stacktraces
## Overview
This page explains what a stacktrace is and how to read it for debugging purposes.
## What is a stacktrace?
* A stacktrace shows the call stack of a thread in Java.
* The call stack represents the path of execution that led to the current point in the program.
* Stacktraces are usually printed to the console when an exception is not handled correctly.
* They are useful for debugging because they show the exact line of code that caused an error and the lines of code that called that line, revealing the path of execution.
## Example
Here's an example of a stacktrace caused by a `NullPointerException`:
[15:20:42 ERROR]: Could not pass event PluginEnableEvent to TestPlugin v1.0 java.lang.NullPointerException: Cannot invoke "Object.toString()" because "player" is null at io.papermc.testplugin.TestPlugin.onPluginEnable(TestPlugin.java:23) ~[TestPlugin-1.0-SNAPSHOT.jar:?] at com.destroystokyo.paper.event.executor.asm.generated.GeneratedEventExecutor1.execute(Unknown Source) ~[?:?] at org.bukkit.plugin.EventExecutor$2.execute(EventExecutor.java:77) ~[paper-api-1.20.2-R0.1-SNAPSHOT.jar:?] at co.aikar.timings.TimedEventExecutor.execute(TimedEventExecutor.java:81) ~[paper-api-1.20.2-R0.1-SNAPSHOT.jar:git-Paper-49] at org.bukkit.plugin.RegisteredListener.callEvent(RegisteredListener.java:70) ~[paper-api-1.20.2-R0.1-SNAPSHOT.jar:?] at io.papermc.paper.plugin.manager.PaperEventManager.callEvent(PaperEventManager.java:54) ~[paper-1.20.2.jar:git-Paper-49] at io.papermc.paper.plugin.manager.PaperPluginManagerImpl.callEvent(PaperPluginManagerImpl.java:126) ~[paper-1.20.2.jar:git-Paper-49] at org.bukkit.plugin.SimplePluginManager.callEvent(SimplePluginManager.java:615) ~[paper-api-1.20.2-R0.1-SNAPSHOT.jar:?] at io.papermc.paper.plugin.manager.PaperPluginInstanceManager.enablePlugin(PaperPluginInstanceManager.java:200) ~[paper-1.20.2.jar:git-Paper-49] at io.papermc.paper.plugin.manager.PaperPluginManagerImpl.enablePlugin(PaperPluginManagerImpl.java:104) ~[paper-1.20.2.jar:git-Paper-49] at org.bukkit.plugin.SimplePluginManager.enablePlugin(SimplePluginManager.java:507) ~[paper-api-1.20.2-R0.1-SNAPSHOT.jar:?] at org.bukkit.craftbukkit.v1_20_R2.CraftServer.enablePlugin(CraftServer.java:636) ~[paper-1.20.2.jar:git-Paper-49] at org.bukkit.craftbukkit.v1_20_R2.CraftServer.enablePlugins(CraftServer.java:547) ~[paper-1.20.2.jar:git-Paper-49] at net.minecraft.server.MinecraftServer.loadWorld0(MinecraftServer.java:636) ~[paper-1.20.2.jar:git-Paper-49] at net.minecraft.server.MinecraftServer.loadLevel(MinecraftServer.java:435) ~[paper-1.20.2.jar:git-Paper-49] at net.minecraft.server.dedicated.DedicatedServer.initServer(DedicatedServer.java:308) ~[paper-1.20.2.jar:git-Paper-49] at net.minecraft.server.MinecraftServer.runServer(MinecraftServer.java:1101) ~[paper-1.20.2.jar:git-Paper-49] at net.minecraft.server.MinecraftServer.lambda$spin$0(MinecraftServer.java:318) ~[paper-1.20.2.jar:git-Paper-49] at java.lang.Thread.run(Thread.java:833) ~[?:?]
* The error occurred when a `PluginEnableEvent` was being handled by `TestPlugin`.
* The cause was a `NullPointerException` when trying to call `toString()` on a null "player" object.
* The error was thrown at line 23 of `TestPlugin.java`.
* The rest of the stacktrace shows server internals, which can generally be ignored in this case.
## Omitted stacktraces
* In JDK 5, the JVM started omitting stacktraces for certain exceptions when the code was optimized.
* To fix this, you can pass the `-XX:-OmitStackTraceInFastThrow` flag to the JVM:
java -XX:-OmitStackTraceInFastThrow -jar paper.jar
---
# Recipes
## Overview
Recipes define how to craft specific items in Minecraft. This page covers how to define recipes using plugins.
## ShapedRecipe
* A shaped recipe requires a specific pattern of items in the crafting grid.
* Created using pattern strings and a map of characters to items.
* Pattern strings are 3, 3-character strings representing the rows of the crafting grid.
Example:
```java
public class TestPlugin extends JavaPlugin {
@Override
public void onEnable() {
NamespacedKey key = new NamespacedKey(this, "television");
ItemStack item = ItemStack.of(Material.BLACK_WOOL);
item.setData(DataComponentTypes.ITEM_NAME, Component.text("Television"));
ShapedRecipe recipe = new ShapedRecipe(key, item);
recipe.shape(
"AAA",
"ABA",
"AAA"
);
recipe.setIngredient('A', Material.WHITE_CONCRETE);
recipe.setIngredient('B', Material.BLACK_STAINED_GLASS_PANE);
getServer().addRecipe(recipe);
}
}
This recipe requires a black stained glass pane surrounded by white concrete to craft a television.
AAA
ABA
AAA
Note:
- Recipes can be registered at any time, not just in
onEnable. - If registering after the plugin has been enabled and players are online, resend recipes to players or use the boolean parameter in
addRecipeto update players.
Caution:
Aircannot be used as a material in a shaped recipe.
ShapelessRecipe
- A shapeless recipe requires a specific number of items in the crafting grid, but not in a specific pattern.
- Created using a list of items.
Example:
public class TestPlugin extends JavaPlugin {
@Override
public void onEnable() {
NamespacedKey key = new NamespacedKey(this, "WarriorSword");
ItemStack item = ItemStack.of(Material.DIAMOND_SWORD);
ShapelessRecipe recipe = new ShapelessRecipe(key, item);
recipe.addIngredient(3, Material.DIAMOND);
recipe.addIngredient(2, Material.STICK);
getServer().addRecipe(recipe);
}
}
This recipe requires 3 diamonds and 2 sticks to craft a diamond sword, without any specific orientation.
Possible crafting grid layouts:
DSS | SDS | S D
D | D | D
D | D | D S
Registries
Overview
This page explains the Registry API in Paper, which is used to manage game data.
Experimental: The Registry API is currently experimental and subject to change.
What is a registry?
- A registry holds a set of values of the same type, each identified by a key.
- Example:
ItemTyperegistry holds all known item types. - Registries are accessed via the
RegistryAccessclass. - Registries are defined by the server and sent to the client, allowing servers and plugins to define custom content.
- Notable examples include enchantments and biomes.
Retrieving values from a registry
- Elements are retrieved using their respective keys.
- Two types of keys:
net.kyori.adventure.key.Key: Represents a namespace and a key.TypedKey: Wraps an Adventure key and includes the key of the registry it belongs to.
Example (retrieving the Sharpness enchantment):
// Fetch the enchantment registry from the registry access
final Registry<Enchantment> enchantmentRegistry = RegistryAccess.registryAccess().getRegistry(RegistryKey.ENCHANTMENT);
// Get the sharpness enchantment using its key.
// getOrThrow may be replaced with get if the registry may not contain said value
final Enchantment enchantment = enchantmentRegistry.getOrThrow(TypedKey.create(RegistryKey.ENCHANTMENT, Key.key("minecraft:sharpness")));
// Same as above, but using the instance's method
final Enchantment enchantment = enchantmentRegistry.getOrThrow(RegistryKey.ENCHANTMENT.typedKey(Key.key("minecraft:sharpness")));
// Same as above, but using generated create method
// available for data-driven registries or "writable" ones
// (those bound to a lifecycle event in RegistryEvents).
final Enchantment enchantment = enchantmentRegistry.getOrThrow(EnchantmentKeys.create(Key.key("minecraft:sharpness")));
// Same as above too, but using generated typed keys.
// Only Vanilla entries have generated keys, for custom entries, the above method must be used.
final Enchantment enchantment = enchantmentRegistry.getOrThrow(EnchantmentKeys.SHARPNESS);
Referencing registry values
- Referencing registry entries can be done using different approaches.
RegistrySet: Defines a collection of elements that relate to a registry.RegistryKeySet: A subtype ofRegistrySetthat holdsTypedKeyinstances. It remains valid even if registry values change.
Example (creating a RegistryKeySet):
// Create a new registry key set that holds a collection enchantments
final RegistryKeySet<Enchantment> bestEnchantments = RegistrySet.keySet(RegistryKey.ENCHANTMENT,
// Arbitrary keys of enchantments to store in the key set.
EnchantmentKeys.CHANNELING,
EnchantmentKeys.create(Key.key("papermc:softspoon"))
);
Tag: Follows the concept of aRegistryKeySetbut is named and can be referenced.
Mutating registries
- Paper allows plugins to modify registries.
Caution:
- Mutating registries must be done during the server's bootstrap phase.
- Only applicable to Paper plugins.
- Exceptions thrown during this phase will cause the server to shut down.
Note:
-
Mutating registries is done via the
LifecycleEventManager. -
The general entry point for mutating registries is the
RegistryEventstype. -
Modification can take two forms: creating new entries and modifying existing entries.
Create new entries
- Done via the
composelifecycle event on registries. - The
composeevent is called after a registry's content has been loaded from vanilla sources and datapacks.
Example (creating a new enchantment):
public class TestPluginBootstrap implements PluginBootstrap {
@Override
public void bootstrap(BootstrapContext context) {
// Register a new handler for the compose lifecycle event on the enchantment registry
context.getLifecycleManager().registerEventHandler(RegistryEvents.ENCHANTMENT.compose().newHandler(event -> {
event.registry().register(
// The key of the registry
// Plugins should use their own namespace instead of minecraft or papermc
EnchantmentKeys.create(Key.key("papermc:pointy")),
b -> b.description(Component.text("Pointy"))
.supportedItems(event.getOrCreateTag(ItemTypeTagKeys.SWORDS))
.anvilCost(1)
.maxLevel(25)
.weight(10)
.minimumCost(EnchantmentRegistryEntry.EnchantmentCost.of(1, 1))
.maximumCost(EnchantmentRegistryEntry.EnchantmentCost.of(3, 1))
.activeSlots(EquipmentSlotGroup.ANY)
);
}));
}
}
Modifying existing entries
- Useful for plugins that want to change the behavior of Vanilla entries.
- Use the
entryAddlifecycle event. - The event is called for any entry added to a registry.
Example (increasing the maximum level of the Sharpness enchantment):
@Override
public void bootstrap(BootstrapContext context) {
context.getLifecycleManager().registerEventHandler(RegistryEvents.ENCHANTMENT.entryAdd()
// Increase the max level to 20
.newHandler(event -> event.builder().maxLevel(20))
// Configure the handler to only be called for the Vanilla sharpness enchantment.
.filter(EnchantmentKeys.SHARPNESS)
);
}
Roadmap
Overview
This page documents planned API changes and potential deprecations in Paper.
Future plans
Interface ItemStacks
- Currently, creating
ItemStacks using the constructor creates an API representation that delegates to an NMS-backed object. - Use
ItemStack#ofto get the NMS-backed object directly. - In the future,
ItemStackwill be converted to an interface, and the constructor will be removed.
Precautions
- Avoid directly extending the
ItemStackclass. Custom implementations are not supported and will break.
ServerPlayer reuse
- Note: Only applies to NMS usage, not API.
- Avoid directly storing player (
ServerPlayer) entity instances. - Currently, the player instance is reused when switching worlds, but this will be reverted to match Vanilla behavior.
- API entities (wrappers) will continue to function, and their underlying instance will be replaced automatically.
Deprecation policy
Caution: Avoid using deprecated APIs. It may cause instability and performance issues.
- API marked with
@Deprecatedshould not be used, as alternative API may be available. - Deprecated API may be marked for removal in the future.
@Deprecated
public void exampleMethod(); // Example deprecated method
Deprecated for removal
- API may be marked as
@DeprecatedandforRemovalwith a@ApiStatus.ScheduledForRemovalversion. - Removal will only occur within major Minecraft release versions.
- Migrate away from API scheduled for removal.
- Adequate time will be given to allow plugin developers to migrate.
@ApiStatus.ScheduledForRemoval(inVersion = "1.20")
@Deprecated(forRemoval = true)
public void exampleMethod(); // Example method marked for removal in 1.20
Deprecation reasons
Common reasons for API deprecation:
Old API
- API represents concepts that no longer exist in the core game.
- May not be functional or may behave unexpectedly.
Duplicate API
- API added by Spigot that clashes with existing Paper API.
- Paper will typically deprecate Spigot's API in favor of its own.
Obsolete API
- Paper has built new APIs to offer as replacements.
- Obsolete API is expected to function for the far future and may not be scheduled for removal for a long time.
paperweight-userdev
Overview
paperweight is Paper's custom build tooling. The paperweight-userdev Gradle plugin provides access to internal code (NMS) during development.
Note: This guide uses the Gradle Kotlin DSL and assumes basic Gradle knowledge. See the [example plugin](link to example plugin) for a fully-functioning example.
Why this is useful
- Only supported way of accessing server internals. Redistributing the server JAR is against the Minecraft EULA.
- Avoids issues with Spigot mappings (pre-1.20.5 Paper), which are a mix of obfuscated and mapped names.
- Allows using fully deobfuscated types, names, and fields during development, then remaps the plugin for use with the obfuscated server.
- Does not apply to reflection. Use a library like [this library](link to reflection library) for non-obfuscated names in reflection.
- As of Minecraft 1.20.5, Paper ships with a Mojang-mapped runtime.
Adding the plugin
Add the plugin to your build.gradle.kts file:
plugins {
id("io.papermc.paperweight.userdev") version "2.0.0-beta.19"
}
Gradle Version: Use the latest stable version of Gradle. See the [Gradle Wrapper documentation](link to Gradle Wrapper documentation) for upgrading.
Keep up to date: The latest version of paperweight-userdev supports dev bundles for Minecraft 1.17.1 and newer.
Support: Only the latest version of paperweight-userdev is officially supported. Ask in the #build-tooling-help channel in the [Discord server](link to Discord server) for issues.
Snapshots: paperweight-userdev SNAPSHOT (pre-release) versions are only available through Paper's Maven repository:
pluginManagement {
repositories {
gradlePluginPortal()
maven("https://repo.papermc.io/repository/maven-public/")
}
}
Adding the dev bundle dependency
Add a dev bundle dependency to your dependencies block in build.gradle.kts:
dependencies {
// Other Dependencies
paperweight.paperDevBundle("1.21.10-R0.1-SNAPSHOT")
}
Tip: Remove any dependency on the Paper API, as the dev bundle includes that.
Configuring the Java toolchain for userdev setup
If the dev bundle doesn't support the Gradle's Java toolchain, configure paperweight's javaLauncher property:
paperweight {
javaLauncher = javaToolchains.launcherFor {
// Example scenario:
// Paper 1.17.1 was originally built with JDK 16
languageVersion = JavaLanguageVersion.of(17)
}
}
Gradle tasks
reobfJar
- Creates a plugin JAR re-obfuscated to Spigot's runtime mappings.
- Works on standard Paper servers.
- Output is in the
build/libsfolder. - The JAR whose filename includes
-devis Mojang-mapped and will not work on most servers.
Shadow
- If the shadow Gradle plugin is applied,
paperweight-userdevwill detect it and use the shaded JAR as input forreobfJar. - The
-dev-all.jarfile inbuild/libsis the shaded, but not re-obfuscated JAR.
Run the reobfJar task on the default build task:
tasks.assemble {
dependsOn(tasks.reobfJar)
}
1.20.5 and beyond
- As of 1.20.5, Paper ships with a Mojang-mapped runtime.
- CraftBukkit classes are no longer relocated into a versioned package.
- Plugins must be deobfuscated before loading when necessary.
Default mappings assumption
- Spigot/Bukkit plugins are assumed to be Spigot-mapped if they don't specify their mappings namespace in the manifest.
- Paper plugins are assumed to be Mojang-mapped if they don't specify their mappings namespace in the manifest.
- Spigot-mapped plugins will need to be deobfuscated on first load.
Compiling to Mojang mappings
- Preferred option: skips the one-time plugin remapping process and may allow version compatibility across smaller updates.
- Makes the plugin incompatible with Spigot servers.
- Remove all
dependsOn(reobfJar)lines and add:
paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArtifactConfiguration.MOJANG_PRODUCTION
Compiling to Spigot mappings
- Add:
paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArtifactConfiguration.REOBF_PRODUCTION
- Useful for plugins that have loaders for both Spigot and Paper.
Note: If using Gradle with the Groovy DSL, access the fields via static methods like getMOJANG_PRODUCTION().
Using databases
Overview
This guide covers using databases in Paper plugins for storing larger amounts of data.
What is a database?
A database is a collection of information stored electronically on a computer system. The main categories are SQL and NoSQL.
NoSQL vs SQL
- NoSQL (Not Only SQL):
- Schema-less and offers flexible data models.
- Designed to handle large volumes of unstructured or semi-structured data.
- Uses various data models (key-value, document, column-family, graph).
- SQL:
- Follows the relational database model.
- Organizes data into structured tables with predefined schemas.
- Uses SQL (Structured Query Language) for interaction.
File-based vs standalone databases
- File-based: Stored in a file on disk, used for smaller databases.
- Standalone: Operates in a separate process, used for larger data models.
File-based databases
- Stored within a single file on disk.
- Easier to set up and use, but offer lesser performance.
- Examples: SQLite and H2.
Simple SQLite Setup
SQLite
Requires a driver to connect/initialize the database.
Note: The JDBC Driver is bundled with Paper.
Usage
Invoke Class#forName(String) on the driver to initialize and create the connection:
public class DatabaseManager {
public void connect() {
Class.forName("org.sqlite.JDBC");
Connection connection = DriverManager.getConnection("jdbc:sqlite:plugins/TestPlugin/database.db");
}
}
You then have access to a Connection object, which can be used to create a Statement and execute SQL queries.
Standalone databases
- Operate in a separate process.
- Harder to set up, but offer better performance.
- Examples: MySQL, MariaDB, and PostgreSQL.
These databases often have connection pooling for improved performance.
Simple MySQL Setup
MySQL
Requires a running MySQL database.
First, add the dependency to your project:
Maven
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>4.0.3</version>
<scope>compile</scope>
</dependency>
Gradle
dependencies {
implementation("com.zaxxer:HikariCP:4.0.3")
}
Caution: The Hikari library is not bundled with Paper, so you will need to shade/relocate it using the [Shadow plugin](link to Shadow plugin). Alternatively, use the library loader.
Usage
public class DatabaseManager {
public void connect() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/mydatabase"); // Address of your running MySQL database
config.setUsername("username"); // Username
config.setPassword("password"); // Password
config.setMaximumPoolSize(10); // Pool size defaults to 10
config.addDataSourceProperty("", ""); // MISC settings to add
HikariDataSource dataSource = new HikariDataSource(config);
try (Connection connection = dataSource.getConnection()) {
// Use a try-with-resources here to autoclose the connection.
PreparedStatement sql = connection.prepareStatement("SQL");
// Execute statement
} catch (Exception e) {
// Handle any exceptions that arise from getting / handing the exception.
}
}
}
Security
SQL Injection
SQL injection is a malicious technique where attackers exploit improper input validation to execute unauthorized SQL commands.
Example:
public void login(String username, String password) {
String sql = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'";
// Execute SQL
}
If the user enters ' OR 1=1; -- as their username, the SQL statement becomes:
SELECT * FROM users WHERE username = '' OR 1=1; -- AND password = 'password'
This will return all users in the database.
Prepared statements
Using prepared statements with PreparedStatement helps prevent SQL injection by separating SQL code from user input using placeholders. Always use prepared statements to ensure security and integrity.
paperweight-userdev
Overview
paperweight is Paper's custom build tooling. The paperweight-userdev Gradle plugin provides access to internal code (NMS) during development.
Note: This guide uses the Gradle Kotlin DSL and assumes basic Gradle knowledge. See the [example plugin](link to example plugin) for a fully-functioning example.
Why this is useful
- Only supported way of accessing server internals. Redistributing the server JAR is against the Minecraft EULA.
- Avoids issues with Spigot mappings (pre-1.20.5 Paper), which are a mix of obfuscated and mapped names.
- Allows using fully deobfuscated types, names, and fields during development, then remaps the plugin for use with the obfuscated server.
- Does not apply to reflection. Use a library like [this library](link to reflection library) for non-obfuscated names in reflection.
- As of Minecraft 1.20.5, Paper ships with a Mojang-mapped runtime.
Adding the plugin
Add the plugin to your build.gradle.kts file:
plugins {
id("io.papermc.paperweight.userdev") version "2.0.0-beta.19"
}
Gradle Version: Use the latest stable version of Gradle. See the [Gradle Wrapper documentation](link to Gradle Wrapper documentation) for upgrading.
Keep up to date: The latest version of paperweight-userdev supports dev bundles for Minecraft 1.17.1 and newer.
Support: Only the latest version of paperweight-userdev is officially supported. Ask in the #build-tooling-help channel in the [Discord server](link to Discord server) for issues.
Snapshots: paperweight-userdev SNAPSHOT (pre-release) versions are only available through Paper's Maven repository:
pluginManagement {
repositories {
gradlePluginPortal()
maven("https://repo.papermc.io/repository/maven-public/")
}
}
Adding the dev bundle dependency
Add a dev bundle dependency to your dependencies block in build.gradle.kts:
dependencies {
// Other Dependencies
paperweight.paperDevBundle("1.21.10-R0.1-SNAPSHOT")
}
Tip: Remove any dependency on the Paper API, as the dev bundle includes that.
Configuring the Java toolchain for userdev setup
If the dev bundle doesn't support the Gradle's Java toolchain, configure paperweight's javaLauncher property:
paperweight {
javaLauncher = javaToolchains.launcherFor {
// Example scenario:
// Paper 1.17.1 was originally built with JDK 16
languageVersion = JavaLanguageVersion.of(17)
}
}
Gradle tasks
reobfJar
- Creates a plugin JAR re-obfuscated to Spigot's runtime mappings.
- Works on standard Paper servers.
- Output is in the
build/libsfolder. - The JAR whose filename includes
-devis Mojang-mapped and will not work on most servers.
Shadow
- If the shadow Gradle plugin is applied,
paperweight-userdevwill detect it and use the shaded JAR as input forreobfJar. - The
-dev-all.jarfile inbuild/libsis the shaded, but not re-obfuscated JAR.
Run the reobfJar task on the default build task:
tasks.assemble {
dependsOn(tasks.reobfJar)
}
1.20.5 and beyond
- As of 1.20.5, Paper ships with a Mojang-mapped runtime.
- CraftBukkit classes are no longer relocated into a versioned package.
- Plugins must be deobfuscated before loading when necessary.
Default mappings assumption
- Spigot/Bukkit plugins are assumed to be Spigot-mapped if they don't specify their mappings namespace in the manifest.
- Paper plugins are assumed to be Mojang-mapped if they don't specify their mappings namespace in the manifest.
- Spigot-mapped plugins will need to be deobfuscated on first load.
Compiling to Mojang mappings
- Preferred option: skips the one-time plugin remapping process and may allow version compatibility across smaller updates.
- Makes the plugin incompatible with Spigot servers.
- Remove all
dependsOn(reobfJar)lines and add:
paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArtifactConfiguration.MOJANG_PRODUCTION
Compiling to Spigot mappings
- Add:
paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArtifactConfiguration.REOBF_PRODUCTION
- Useful for plugins that have loaders for both Spigot and Paper.
Note: If using Gradle with the Groovy DSL, access the fields via static methods like getMOJANG_PRODUCTION().
Scheduling
Overview
This page explains how to schedule tasks in Paper plugins.
What is a tick?
[Content missing - Document incomplete]
```markdown
# Scheduling in Bukkit
The `BukkitScheduler` is used to schedule code execution later or repeatedly.
**Note:** This guide is for non-Folia Bukkit servers. Folia servers should use their respective schedulers.
## What is a Tick?
* Every game runs a game loop to execute game logic repeatedly.
* In Minecraft, a single execution of this loop is called a 'tick'.
* Minecraft runs at 20 ticks per second, or one tick every 50 milliseconds.
* If a tick takes longer than 50ms, the server lags.
* A task scheduled to run after 100 ticks will run after 5 seconds (at 20 TPS). If the server is running at 10 TPS, it will take 10 seconds.
## Converting Between Human Units and Minecraft Ticks
* Scheduler methods use ticks as the unit of time for delay or period.
* Conversion formulas:
* `ticks = seconds * 20`
* `seconds = ticks / 20`
* Using `TimeUnit` for readability:
```java
TimeUnit.MINUTES.toSeconds(5) * 20 // Converts 5 minutes to ticks
TimeUnit.SECONDS.toMinutes(ticks / 20) // Converts ticks to minutes
```
* Using `Tick` class from Paper:
```java
Tick.tick().fromDuration(Duration.ofMinutes(5)) // Converts 5 minutes to ticks, yields 6000 ticks
```
## Obtaining the Scheduler
* Use the `getScheduler` method on the `Server` class.
```java
@Override
public void onEnable() {
BukkitScheduler scheduler = this.getServer().getScheduler();
}
```
## Scheduling Tasks
Scheduling a task requires passing the following:
* Your plugin's instance.
* The code to run, as either a `Runnable` or `Consumer<BukkitTask>`.
* The delay in ticks before the first execution.
* The period in ticks between executions for repeating tasks.
## Difference Between Synchronous and Asynchronous Tasks
### Synchronous Tasks (On the Main Thread)
* Executed on the main server thread, which handles all game logic.
* Tasks on the main thread affect server performance.
* Use asynchronous tasks for time-consuming operations like web requests, file access, or database interactions.
### Asynchronous Tasks (Off the Main Thread)
* Executed on separate threads, minimizing impact on server performance.
* **Caution:** Large parts of the Bukkit API are not thread-safe and should not be used in asynchronous tasks if they change or access the world state.
* **Note:** Asynchronous tasks are still started from the main thread and affected by server lag.
* For a scheduler independent of the server, use a `ScheduledExecutorService`. See [this guide](link to guide).
## Different Ways to Schedule Tasks
### Using `Runnable`
* The `Runnable` interface is for simple tasks not requiring a `BukkitTask` instance.
* Can be implemented in a separate class:
```java
// MyRunnableTask.java
public class MyRunnableTask implements Runnable {
private final MyPlugin plugin;
public MyRunnableTask(MyPlugin plugin) {
this.plugin = plugin;
}
@Override
public void run() {
this.plugin.getServer().broadcast(Component.text("Hello, World!"));
}
}
scheduler.runTaskLater(plugin, new MyRunnableTask(plugin), 20);
```
* Or use a lambda expression:
```java
scheduler.runTaskLater(plugin,
/* Lambda: */
() -> {
this.plugin.getServer().broadcast(Component.text("Hello, World!"));
},
/* End of the lambda */
20
);
```
### Using `Consumer<BukkitTask>`
* The `Consumer` interface is for tasks needing a `BukkitTask` instance (e.g., repeated tasks where you want to cancel the task from within).
* Implement it in a separate class:
```java
// MyConsumerTask.java
public class MyConsumerTask implements Consumer<BukkitTask> {
private final UUID entityId;
public MyConsumerTask(UUID uuid) {
this.entityId = uuid;
}
@Override
public void accept(BukkitTask task) {
Entity entity = Bukkit.getServer().getEntity(this.entityId);
if (entity instanceof LivingEntity livingEntity) {
livingEntity.addPotionEffect(new PotionEffect(PotionEffectType.SPEED, 20, 1));
return;
}
task.cancel();
// The entity is no longer valid, there's no point in continuing to run this task
}
}
scheduler.runTaskTimer(plugin, new MyConsumerTask(someEntityId), 0, 20);
```
* Or use a lambda expression:
```java
scheduler.runTaskTimer(plugin,
/* Lambda: */
task -> {
Entity entity = Bukkit.getServer().getEntity(entityId);
if (entity instanceof LivingEntity livingEntity) {
livingEntity.addPotionEffect(new PotionEffect(PotionEffectType.SPEED, 20, 1));
return;
}
task.cancel();
// The entity is no longer valid, there's no point in continuing to run this task
}
/* End of the lambda */
, 0, 20
);
```
### Using `BukkitRunnable`
* `BukkitRunnable` implements `Runnable` and holds a `BukkitTask` instance, allowing you to use `this.cancel()` within the `run()` method.
```java
// CustomRunnable.java
public class CustomRunnable extends BukkitRunnable {
private final UUID entityId;
public CustomRunnable(UUID uuid) {
this.entityId = uuid;
}
@Override
public void run() {
Entity entity = Bukkit.getServer().getEntity(this.entityId);
if (entity instanceof LivingEntity livingEntity) {
livingEntity.addPotionEffect(new PotionEffect(PotionEffectType.SPEED, 20, 1));
return;
}
this.cancel();
// The entity is no longer valid, there's no point in continuing to run this task
}
}
// Example usage (not shown in the original document but inferred)
new CustomRunnable(someEntityId).runTaskTimer(plugin, 0, 20); // Adds a potion effect until the entity dies.
```
### Using a Delay of 0 Ticks
* A delay of 0 ticks runs the task on the next tick.
* If scheduled during server startup or before the server is enabled, the task executes before the server is enabled.
---
# Using Databases
When storing larger amounts of data inside a plugin, it is recommended to use a database.
## What is a Database?
A database is a collection of information stored electronically on a computer system. The main two categories are SQL and NoSQL.
## NoSQL vs SQL
* **NoSQL (Not Only SQL)**:
* Schema-less and offers flexible data models.
* Designed to handle large volumes of unstructured or semi-structured data.
* Uses data models like key-value, document, column-family, or graph.
* **SQL**:
* Follows the relational database model.
* Organizes data into structured tables with predefined schemas.
* Uses SQL (Structured Query Language) to interact with the database.
## File-based vs Standalone Databases
* **File-based**: Stored in a file on disk, used for smaller databases.
* **Standalone**: Operate in a separate process, used for larger data models.
## File-based Databases
* Stored within a single file on the disk.
* Easier to set up and use, suitable for smaller databases.
* Offer lesser performance than standalone databases.
* Examples: SQLite and H2.
### Simple SQLite Setup
#### SQLite
* Requires a driver to connect to/initialize the database.
* **Note:** The JDBC Driver is bundled with Paper, so you don't need to shade/relocate it.
#### Usage
* Invoke `Class#forName(String)` on the driver to allow it to initialize and then create the connection to the database:
```java
// DatabaseManager.java
public class DatabaseManager {
public void connect() {
try {
Class.forName("org.sqlite.JDBC");
Connection connection = DriverManager.getConnection("jdbc:sqlite:plugins/TestPlugin/database.db");
// You then have access to a Connection object,
// which you can use to create a Statement and execute SQL queries.
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace(); // Handle exceptions appropriately
}
}
}
```
* You then have access to a `Connection` object, which you can use to create a `Statement` and execute SQL queries.
* Learn more about the Java Database Connectivity API [here](link to Java Database Connectivity API).
## Standalone Databases
* Operate in a separate process.
* Harder to set up and use, but offer better performance than file-based databases.
* Examples: MySQL, MariaDB, and PostgreSQL.
* Connectors often have connection pooling:
* Creates a pool of pre-established and reusable database connections.
* Reduces the overhead of creating and tearing down connections repeatedly.
* Improves application performance and scalability.
### Simple MySQL Setup
#### MySQL
* Requires more steps but offers performance benefits for larger databases.
* This is a short setup guide for using the `Hikari` library with MySQL.
* **Note:** Requires a running MySQL database to connect to.
* First, add the dependency to your project:
##### Maven
```xml
<!-- pom.xml -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>4.0.3</version>
<scope>compile</scope>
</dependency>
```
##### Gradle
```kotlin
// build.gradle(.kts)
dependencies {
implementation("com.zaxxer:HikariCP:4.0.3")
}
```
* **Caution:** The Hikari library is not bundled with Paper, so you will need to shade/relocate it. Use the Shadow plugin in Gradle.
* Alternatively, you can use the library loader with your Paper plugin to load the library at runtime. See [here](link to Paper plugin library loader) for more information.
#### Usage
* Once you have the dependency added, you can work with the connector in your code:
```java
// DatabaseManager.java
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class DatabaseManager {
private HikariDataSource dataSource;
public void connect() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/mydatabase"); // Address of your running MySQL database
config.setUsername("username"); // Username
config.setPassword("password"); // Password
config.setMaximumPoolSize(10); // Pool size defaults to 10
config.addDataSourceProperty("cachePrepStmts", "true"); // Example MISC setting
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
dataSource = new HikariDataSource(config);
}
public void executeStatement(String sql) {
try (Connection connection = dataSource.getConnection();
PreparedStatement statement = connection.prepareStatement(sql)) {
statement.execute();
} catch (SQLException e) {
e.printStackTrace(); // Handle any exceptions that arise from getting / handing the exception.
}
}
public void close() {
if (dataSource != null) {
dataSource.close();
}
}
}
```
## Security
### SQL Injection
* SQL injection is a malicious technique that exploits improper input validation to execute unauthorized SQL commands.
* Can cause data breaches or damage to the database.
* **Example Vulnerable Code:**
```java
public void login(String username, String password) {
String sql = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'";
// Execute SQL
}
```
* **Exploit Example:**
If the user enters the following as their username: `' OR 1=1; --`
The SQL statement becomes:
```sql
SELECT * FROM users WHERE username = '' OR 1=1; -- AND password = 'password'
```
This returns all users in the database.
### Prepared Statements
* Using prepared statements with `PreparedStatement`s helps prevent SQL injection.
* Separates SQL code from user input using placeholders.
* Reduces the risk of executing unintended SQL commands.
* **Always use prepared statements for security and data integrity.**
* Read more about SQL injection [here](link to SQL injection resource).