Creating First Item Modifier
Item Modifiers allow you to dynamically alter item properties, based on the holding player's race.
This guide walks you through creating, registering, configuring, and customizing your first Item Modifier in JustRaces.
IMPORTANT
Don't confuse with Vanilla Minecraft's Item Modifiers
Lifecycle Concepts: Apply & Undo
Every item modifier operates on a strict Apply / Undo dynamic lifecycle:
apply(ItemStack item): Invoked when an item enters the player's inventory, when picking up items, or when race traits are applied/reloaded.undo(ItemStack item): Invoked when the item leaves the player's inventory (dropping, moving to a chest, changing races, or dying).
Because undo() triggers the exact moment an item leaves a player's possession, race-specific item properties cannot be smuggled or transferred to other players or different races.
resetData vs unsetData
When reverting changes in undo(), always prefer Paper's item.resetData(...) over item.unsetData(...):
resetData(...): Restores the component to its default baseline state for that specific item type.unsetData(...): Completely strips the component from the item entirely, which can break default item behaviors.
Creating the Item Modifer Class
To create a custom modifier, extend BaseItemModifier and implement your apply and undo methods.
public class ExampleItemModifier extends BaseItemModifier {
@Override
public NamespacedKey getKey() {
return new NamespacedKey("example", "test_modifier");
}
@Override
public void apply(ItemStack item) {
// Makes the item unbreakable while held by a player of this race
item.setData(DataComponentTypes.UNBREAKABLE);
}
@Override
public void undo(ItemStack item) {
// Reverts unbreakable status when the item leaves the player's inventory
item.resetData(DataComponentTypes.UNBREAKABLE);
}
}Registering Item Modifier
Register your item modifier during plugin initialization before loading any race resources.
public class ItemModifiersRegistry {
public static void register() {
registerItemModifier(new ExampleItemModifier());
}
private static void registerItemModifier(BaseItemModifier modifier) {
JustRacesRegistries.ITEM_MODIFIERS.register(modifier.getKey(), modifier);
}
}Then call your registry in your main plugin class:
public final class ExampleAddon extends JavaPlugin {
@Override
public void onEnable() {
ItemModifiersRegistry.register();
}
}Merging Data Components
Instead of completely overwriting existing Data Components, BaseItemModifier provides the mergeComponent helper method. This allows you to append builder-based properties (such as custom consumable effects) without wiping the item's existing data.
public class ExampleItemModifier extends BaseItemModifier {
@Override
public NamespacedKey getKey() {
return new NamespacedKey("example", "test_modifier");
}
@Override
public void apply(ItemStack item) {
mergeComponent(item, DataComponentTypes.CONSUMABLE, getConsumable(), Consumable.consumable());
}
private static @NonNull Consumer<Consumable.Builder> getConsumable() {
return builder -> builder
.addEffect(ConsumeEffect.clearAllStatusEffects());
}
@Override
public void undo(ItemStack item) {
item.resetData(DataComponentTypes.CONSUMABLE);
}
}Adding Item Modifier Configurations
To make your item modifier customizable via JSON instead of hardcoding values in Java Class, implement the ConfigurableItemModifier interface.
public class ExampleItemModifier extends BaseItemModifier implements ConfigurableItemModifier {
@Override
public NamespacedKey getKey() {
return new NamespacedKey("example", "test_modifier");
}
@Override
public void apply(ItemStack item) {
int maxStackSize = Math.clamp(getConfigInt("max_stack_size"), 1, 99);
item.setData(DataComponentTypes.MAX_STACK_SIZE, maxStackSize);
}
@Override
public void undo(ItemStack item) {
item.resetData(DataComponentTypes.MAX_STACK_SIZE);
}
}JSON Configuration File
Create a corresponding JSON file inside your plugin's resources/item_modifiers/ folder matching your key namespace:
resources/item_modifiers/test_modifier.json
{
"max_stack_size": 16
}