Loot Table Modifications 26.2
A guide for modifying loot tables using events provided by the Fabric API.
The loot table system determines what items are dropped when a block is broken, an entity is killed, or a chest is opened. Fabric API gives you several ways to modify, replace, and post-process loot tables during loading, and to adjust the final drops at runtime.
The Fabric Loot API provides several events through the LootTableEvents class:
LootTableEvents.MODIFY: Keep the original loot table and add pools and entries.LootTableEvents.REPLACE: Discard the original table and provide a new one.LootTableEvents.MODIFY_DROPS: Change the final list ofItemStackdrops after loot has been generated.LootTableEvents.ALL_LOADED: Inspect or validate all tables after loading is complete.
These events occur in a specific order during the loot table loading process:
REPLACEMODIFYALL_LOADEDMODIFY_DROPS
MODIFY_DROPS is the only event that occurs during runtime, while the others happen during loading the world.
When using these events, remembering this order is important, as it affects how your changes interact with other events and the original loot tables.
Modifying Loot Tables
Use LootTableEvents.MODIFY when you want to change an existing loot table while keeping its original contents intact. The callback gives you a LootTable.Builder, so you can add new pools or add entries to existing pools without rebuilding the whole table.
This is usually the best choice for adding items to vanilla or data-pack tables, such as adding a custom item to a block's existing drops. You can inspect the source parameter to see whether the table came from built-in resources, a data pack, or another replacement event.
Use MODIFY when the original loot table should remain mostly intact.
For example, let's use the MODIFY event to make killing a white sheep with a diamond sword drop a diamond by using the LootItemEntityPropertyCondition predicate.
java
LootTableEvents.MODIFY.register((key, tableBuilder, source, registries) -> {
// If the loot table is for a white sheep, and it is not overridden by a user:
if (source.isBuiltin() && BuiltInLootTables.SHEEP.white().equals(key)) {
// Create a new loot pool that will hold the diamonds.
LootPool.Builder pool = LootPool.lootPool()
// Add diamonds...
.add(LootItem.lootTableItem(Items.DIAMOND))
// ...only if the sheep was killed with a diamond sword.
.when(LootItemEntityPropertyCondition.hasProperties(
LootContext.EntityTarget.ATTACKER,
EntityPredicate.Builder.entity().equipment(
EntityEquipmentPredicate.Builder.equipment().mainhand(
ItemPredicate.Builder.item().of(
registries.lookupOrThrow(Registries.ITEM),
Items.DIAMOND_SWORD
)
)
)
));
// Add the loot pool to the loot table
tableBuilder.withPool(pool);
}
});1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Effects of the above example:
Replacing Loot Tables
Use LootTableEvents.REPLACE when you want to discard an existing loot table and provide a new one.
The callback receives the original LootTable. Return a new LootTable to replace it, or return null to keep it intact. Once a listener replaces a table, no other replacement listeners will be called on it.
This event is useful when the original table is incompatible with your mod's behavior and modifying individual loot pools would be more complicated than creating a new table.
For example, let's use the REPLACE event to replace the loot table for a brown sheep with a new table that drops a gold ingot when killed with a golden sword by using the LootItemEntityPropertyCondition predicate.
java
LootTableEvents.REPLACE.register((key, original, source, holder) -> {
// If the loot table is for a brown sheep, and it is not overridden by a user:
if (source.isBuiltin() && BuiltInLootTables.SHEEP.brown().equals(key)) {
// Create a new loot pool that will hold the gold ingot.
LootPool.Builder pool = LootPool.lootPool()
// Add gold ingot...
.add(LootItem.lootTableItem(Items.GOLD_INGOT))
// ...only if the sheep was killed with a golden sword.
.when(LootItemEntityPropertyCondition.hasProperties(
LootContext.EntityTarget.ATTACKER,
EntityPredicate.Builder.entity().equipment(
EntityEquipmentPredicate.Builder.equipment().mainhand(
ItemPredicate.Builder.item().of(
holder.lookupOrThrow(Registries.ITEM),
Items.GOLDEN_SWORD
)
)
)
));
// Create a new loot table with the loot pool
return LootTable.lootTable().withPool(pool).build();
}
return null;
});1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
WARNING
Always return null if you are not replacing a loot table. Returning the original table still marks the loot table as replaced, which prevents later replacement listeners from running and fails the isBuiltIn() check.
Effects of the above example:
Modifying Loot Table Drops
LootTableEvents.MODIFY_DROPS doesn't run until the loot has been generated at runtime. This event is useful when:
- The number of loot tables is unknown or numerous.
- The same rules should apply to many loot tables.
- You want to inspect the
LootContext, such as the entity, tool, or damage source. - Adding a custom loot function to every table would be inconvenient.
The drops list can be modified directly by adding, removing, or changing item stacks. Note that, because this event runs after loot generation, it cannot change the loot table's pools, entries, or conditions.
INFO
The drops may already be separated into stacks if the loot table requested a particular stack size.
For example, let's use the MODIFY_DROPS event to make breaking a stone block drop two stone blocks instead of cobblestone.
java
LootTableEvents.MODIFY_DROPS.register((holder, context, drops) -> {
// Replace cobblestone drop in stone loot table with stone in the drops.
if (holder.is(Blocks.STONE.getLootTable().orElseThrow())) {
// Only replace if cobblestone is actually present, so we don't override already modified drops.
if (drops.removeIf(stack -> stack.is(Items.COBBLESTONE))) {
drops.add(new ItemStack(Items.STONE, 2));
}
}
});1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
Effects of the above example:
Loot Table Post-Processing
Use LootTableEvents.ALL_LOADED for work that should happen after every loot table has been loaded, and after the REPLACE and MODIFY events have triggered.
The event provides the server's ResourceManager and the complete loot table registry. This makes it suitable for inspecting loaded tables, validating them, collecting information, or performing additional setup that depends on all tables being available.
INFO
This event is not used to add drops during loot generation. For changing a table, use MODIFY or REPLACE. For changing generated item stacks, use MODIFY_DROPS.
java
LootTableEvents.ALL_LOADED.register((resourceManager, lootRegistry) -> {
Optional<LootTable> blueWoolTable = lootRegistry.getOptional(Blocks.WOOL.blue().getLootTable().orElse(null));
// Log a warning if the blue wool loot table is empty or missing.
if (blueWoolTable.isEmpty() || blueWoolTable.get() == LootTable.EMPTY) {
ExampleMod.LOGGER.warn("blue wool loot table should not be empty");
}
});1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
Predicates
Loot conditions, internally called predicates, control whether a loot pool, entry, or function can be used. They are especially useful with MODIFY and REPLACE, where they let you make added or replacement drops conditional without handling every case in Java code. See the .when(...) calls in the MODIFY and REPLACE examples above. The same conditions can help when designing replacement tables, while MODIFY_DROPS requires equivalent checks to be performed in the event callback.
Below are examples of commonly used predicates, grouped by their purpose. See the net.minecraft.world.level.storage.loot.predicates package and the Minecraft Wiki predicate list for the full set:
Logic Predicates
These combine or invert other conditions.
AllOfCondition: every condition must pass. Use this when you want an AND check.AnyOfCondition: at least one condition must pass. Use this when you want an OR check.InvertedLootItemCondition: flips another condition so it passes only when the original one fails. Use this when you need NOT logic.
TIP
These logical predicates can be nested to create complex conditions. For example, you can combine AllOfCondition and AnyOfCondition to create a condition that requires multiple checks to pass, while allowing for some flexibility in the requirements.
World-State Predicates
These check things about the world or the position where loot is generated.
WeatherCheck: checks whether it is raining or thundering.TimeCheck: checks the time of day or a time range.LocationCheck: checks where the drop happened, such as the Y level or other location data.EnvironmentAttributeCheck: checks world-specific environment rules or attributes.
Block, Tool, and Entity Predicates
These look at the block being broken, the tool being used, or the entity that caused the loot.
ExplosionCondition: makes a loot pool or entry apply only when the drop survives an explosion.MatchTool: checks whether the tool used to break a block matches a given item or item predicate.LootItemBlockStatePropertyCondition: checks the block state before it was broken, which is useful for crops and other stateful blocks.LootItemKilledByPlayerCondition: requires the entity to have been killed by a player.DamageSourceCondition: checks details about the damage source, such as whether the hit was direct or indirect.
Chance-Based Predicates
These decide drops by probability or by enchantment level.
LootItemRandomChanceCondition: gives a flat random chance for a drop.LootItemRandomChanceWithEnchantedBonusCondition: changes the chance based on enchantment level.BonusLevelTableCondition: a helper for enchantment-scaled loot chances, such as Fortune.
WARNING
LootItemRandomChanceWithEnchantedBonusCondition and LootItemRandomChanceCondition should not be used together in the same pool, as both of them define base chance and may cause unintended behavior.
Shared Predicate References
ConditionReference: points to a data-driven loot condition defined elsewhere and reused in multiple tables.



