Skip to main content
Version: 26.3

Loot Events

dev.architectury.event.events.common.LootEvent

Modify or replace loot tables as they load.

Events

EventListener methodReturns
MODIFY_LOOT_TABLEmodifyLootTable(HolderGetter.Provider registries, ResourceKey<LootTable> key, LootTableModificationContext context, boolean builtin)void - add pools to the existing table.
REPLACE_LOOT_TABLEreplaceLootTable(HolderGetter.Provider registries, ResourceKey<LootTable> key, LootTable original)CompoundEventResult<LootTable> - interrupt to swap the whole table.

The registries parameter

Both listeners receive a HolderGetter.Provider, which resolves the holders and tags a loot table refers to through lookupOrThrow, get(ResourceKey) and get(TagKey). It cannot enumerate registries or build a RegistryOps.

Do not cast it to HolderLookup.Provider

The parameter was widened from HolderLookup.Provider to HolderGetter.Provider in Architectury API 22.0, because the loaders genuinely differ here. Fabric's LootTableEvents passes a real HolderLookup.Provider, so an instanceof check succeeds there. NeoForge passes a method reference that implements HolderGetter.Provider and nothing else, so the same check is always false and a cast always throws.

A listener that recovers the wider type therefore works on Fabric and fails on every NeoForge install. If you need a RegistryOps to deserialize loot content, build it outside these events from a provider you own.

Adding to a table

Use context.addPool(LootPool.Builder) to add a pool to the table.

The builtin flag tells you where the table came from: true means it's built in to vanilla or a mod; false means it's from a user data pack. A common pattern is to only modify built-in tables, so user data packs can fully override them.

note

On NeoForge, builtin is always true. The event does run for every loot table there, but LootTableLoadEvent does not expose where a table came from, so data pack tables cannot be told apart from built-in ones. Only Fabric reports the real value.

Example: add diamonds to the dirt loot table

LootEvent.MODIFY_LOOT_TABLE.register((registries, key, context, builtin) -> {
if (builtin && Blocks.DIRT.getLootTable().equals(Optional.ofNullable(key))) {
LootPool.Builder pool = LootPool.lootPool()
.add(LootItem.lootTableItem(Items.DIAMOND));
context.addPool(pool);
}
});

Replacing a table

Where MODIFY_LOOT_TABLE appends pools to the existing table, REPLACE_LOOT_TABLE swaps the whole table for a different one. Interrupt the CompoundEventResult with the replacement table to take effect, or return CompoundEventResult.pass() to leave the table alone. The first listener to interrupt wins, and later listeners still see the original table.

This mirrors NeoForge's LootTableLoadEvent#setTable and Fabric's LootTableEvents.REPLACE.

Example: replace a table outright

LootEvent.REPLACE_LOOT_TABLE.register((registries, key, original) -> {
if (key.identifier().equals(MyMod.id("chests/my_structure"))) {
return CompoundEventResult.interruptTrue(buildMyTable());
}
return CompoundEventResult.pass();
});