Skip to main content
Version: 1.21.x

Loot Tables

Loot tables are data files that are used to define randomized loot drops. A loot table can be rolled, returning a (potentially empty) list of item stacks. The output of this process depends on (pseudo-)randomness. Loot tables are located at data/<mod_id>/loot_tables/<name>.json. For example, the loot table minecraft:blocks/dirt, used by the dirt block, is located at data/minecraft/loot_tables/blocks/dirt.json.

Minecraft uses loot tables at various points in the game, including block drops, entity drops, chest loot, fishing loot, and many others. How a loot table is referenced depends on the context:

  • Every block will, by default, receive an associated loot table, located at <block_namespace>:blocks/<block_name>. This can be disabled by calling #noLootTable on the block's Properties, resulting in no loot table being created and the block dropping nothing; this is mainly done by air-like or technical blocks.
  • Every subclass of LivingEntity that is not in the MobCategory.MISC category (as determined by EntityType#getCategory) will, by default, receive an associated loot table, located at <entity_namespace>:entities/<entity_name>. This can be changed by overriding #getLootTable if you are directly extending LivingEntity, or by overriding #getDefaultLootTable if you are extending Mob or a subclass thereof. For example, sheep use this to roll different loot tables depending on their wool color.
  • Chests in structures specify their loot table in their block entity data. Minecraft stores all chest loot tables in minecraft:chests/<chest_name>; it is recommended, but not required to follow this practice in mods.
  • The loot tables for gift items that villagers may throw at players after a raid are defined in the neoforge:raid_hero_gifts data map.
  • Other loot tables, for example the fishing loot table, are retrieved when needed from level.getServer().reloadableRegistries().getLootTable(lootTableId). A list of all vanilla loot table locations can be found in BuiltInLootTables.
warning

Loot tables should generally only be created for stuff that belongs to your mod. For modifying existing loot tables, global loot modifiers (GLMs) should be used instead.

Due to the complexity of the loot table system, loot tables are compromised of several sub-systems that each have a different purpose.

Loot Entry

A loot entry (or loot pool entry), represented in code through the abstract LootPoolEntryContainer class, is a singular loot element. It can specify one or multiple items to be dropped.

Vanilla provides a total of 8 different loot entry types. Through the common LootPoolEntryContainer superclass, all of them have the following properties:

  • weight: The weight value. Defaults to 1. This is used for cases where some items should be more common than others. For example, given two loot entries, one with weight 3 and one with weight 1, then there is a 75% chance for the first entry to be chosen, and a 25% chance for the second entry.
  • quality: The quality value. Defaults to 0. If this is non-zero, then this value is multiplied by the luck value (set in the loot context) and added to the weight when rolling the loot table.
  • conditions: A list of loot conditions to apply to this loot entry. If one condition fails, the entry is not loaded and is treated as if it weren't present.
  • functions: A list of loot functions to apply to the outputs of this loot entry.

Loot entries are generally split into two groups: singletons (with the common superclass LootPoolSingletonContainer) and composites (with the common superclass CompositeEntryBase), where composites are made up of multiple singletons. The following singleton types are provided by Minecraft:

  • minecraft:empty: An empty loot entry, representing no item. Created in code by calling EmptyLootItem#emptyItem.
  • minecraft:item: A singular loot item entry, dropping the specified item when rolled. Created in code by calling LootItem#lootTableItem with the desired item.
    • Setting stack size, data components, etc. can be done using loot functions.
  • minecraft:tag: A tag entry, dropping all items in the specified tag when rolling. Has two variants, depending on the value of the boolean expand property. If expand is true, a separate entry for each item in the tag is generated, otherwise one entry is used to drop all items. Created by calling TagEntry#tagContents (for expand=false) or TagEntry#expandTag (for expand=true), each with an item tag key parameter.
    • For example, if expand is true and the tag is #minecraft:planks, one entry is generated for each planks type (so 11 entries for the 11 vanilla planks + one entry per modded planks), each with the specified weight, quality and functions; whereas if expand is false, one single entry dropping all planks is used.
  • minecraft:dynamic: A loot entry referencing a dynamic drop. Dynamic drops are a system to add entries to a loot table that cannot be specified beforehand, instead adding them in code. A dynamic drops entry consists of an id and a Consumer<ItemStack> that actually adds the items. To add a dynamic drops entry, specify a minecraft:dynamic entry with the desired id and then add a corresponding consumer in the loot context. Created using DynamicLoot#dynamicEntry.
  • minecraft:loot_table: A loot entry that rolls another loot table, adding the result of that loot table as a single entry. The other loot table can either be specified by id or be inlined as a whole. Created in code by calling NestedLootTable#lootTableReference with a ResourceLocation parameter, or NestedLootTable#inlineLootTable with a LootTable object parameter for an inline loot table.

The following composite types are provided by Minecraft:

  • minecraft:group: A loot entry containing a list of other loot entries, which are run in order. Created in code by calling EntryGroup#list, or by calling #append on another LootPoolSingletonContainer.Builder, each with other loot entry builders.
  • minecraft:sequence: Like minecraft:group, but the loot entry stops running as soon as one sub-entry fails, discarding all entries after that. Created in code by calling SequentialEntry#sequential, or by calling #then on another LootPoolSingletonContainer.Builder, each with other loot entry builders.
  • minecraft:alternatives: Sort of an opposite to minecraft:sequence, but the loot entry stops running as soon as one sub-entry succeeds (instead of as soon as one fails), discarding all entries after that. Created in code by calling AlternativesEntry#alternatives, or by calling #otherwise on another LootPoolSingletonContainer.Builder, each with other loot entry builders.

Custom Loot Entry Types

It is also possible for modders to add custom loot entry types. Loot entry types are a [registry], so we can simply create a DeferredRegister and register to it. For the sake of example, we want to create a loot entry type that returns the drops of a entity - this is purely for example purposes, in practice it would be more ideal to directly reference the other loot table. Let's start by creating our loot entry type class:

// We extend LootPoolSingletonContainer since we have a "finite" set of drops.
// Some of this code is adapted from NestedLootTable.
public class EntityLootEntry extends LootPoolSingletonContainer {
// A Holder for the entity type we want to roll the other table for.
private final Holder<EntityType<?>> entity;

// It is common practice to have a private constructor and have a static factory method.
// This is because weight, quality, conditions, and functions are supplied by a lambda below.
private EntityLootEntry(Holder<EntityType<?>> entity, int weight, int quality, List<LootItemCondition> conditions, List<LootItemFunction> functions) {
// Pass lambda-provided parameters to super.
super(weight, quality, conditions, functions);
// Set our values.
this.entity = entity;
}

// Static builder method, accepting our custom parameters and combining them with a lambda that supplies the values common to all entry types.
public static LootPoolSingletonContainer.Builder<?> entityLoot(Holder<EntityType<?>> entity) {
// Use the static simpleBuilder() method defined in LootPoolSingletonContainer.
return simpleBuilder((weight, quality, conditions, functions) -> new EntityLootEntry(entity, weight, quality, conditions, functions));
}

// This is where the magic happens. To add an item stack, we generally call #accept on the consumer.
// However, in this case, we let #getRandomItems do that for us.
@Override
public void createItemStack(Consumer<ItemStack> consumer, LootContext context) {
// Get the entity's loot table. If it doesn't exist, an empty loot table will be returned, so null-checking is not necessary.
LootTable table = context.getLevel().reloadableRegistries().getLootTable(entity.value().getDefaultLootTable());
// Use the raw version here, because vanilla does it too. :P
// #getRandomItemsRaw calls consumer#accept for us on the results of the roll.
table.getRandomItemsRaw(context, consumer);
}
}

Next up, we create a MapCodec for our loot table:

// This is placed as a constant in EntityLootEntry.
public static final MapCodec<EntityLootEntry> CODEC = RecordCodecBuilder.mapCodec(inst ->
// Add our own fields.
inst.group(
// A value referencing an entity type id.
ResourceKey.codec(Registries.ENTITY_TYPE).fieldOf("entity").forGetter(e -> e.entity)
)
// Add common fields: weight, display, conditions, and functions.
.and(singletonFields(inst))
.apply(inst, EntityLootEntry::new)
);

We then use this codec in registration:

public static final DeferredRegister<LootPoolEntryType> LOOT_POOL_ENTRY_TYPES =
DeferredRegister.create(Registries.LOOT_POOL_ENTRY_TYPE, ExampleMod.MOD_ID);

public static final Supplier<LootPoolEntryType> ENTITY_LOOT =
LOOT_POOL_ENTRY_TYPES.register("entity_loot", () -> new LootPoolEntryType(EntityLootEntry.CODEC));

Finally, in our loot entry class, we must override getType():

public class EntityLootEntry extends LootPoolSingletonContainer {
// other stuff here

@Override
public LootPoolEntryType getType() {
return ENTITY_LOOT.get();
}
}

Loot Pool

A loot pool is, in essence, a list of loot entries. Loot tables can contain multiple loot pools, each loot pool will be rolled independently of the others.

Loot pools may contain the following contents:

  • entries: A list of loot entries.
  • conditions: A list of loot conditions to apply to this loot pool. If one condition fails, the loot pool is not loaded and none of its entries will be rolled.
  • functions: A list of loot functions to apply to all loot entry outputs of this loot pool.
  • rolls and bonus_rolls: Two number providers (read on) that together determine the amount of times this loot pool will be rolled. The formula is rolls + bonus_rolls * luck, where the luck value is set in the loot parameters.
  • name: A name for the loot pool. NeoForge-added. This can be used by GLMs. If unspecified, this is the hash code of the loot pool, prefixed by custom#.

Number Provider

Number providers are a way to get (pseudo-)randomized numbers in a datapack context. Primarily used by loot tables, they are also used in other contexts, for example in worldgen. Vanilla provides the following six number providers:

  • minecraft:constant: A constant float value, rounding to integer where needed. Created through ConstantValue#exactly.
  • minecraft:uniform: Uniformly-distributed random integer or float values, with min and max values set. All values between min and max have the same chance to appear. Created through UniformGenerator#between.
  • minecraft:binomial: Binomially-distributed random integer values, with n and p values set. See Binomial Distribution for more information on what these values mean. Created through BinomialDistributionGenerator#binomial.
  • minecraft:score: Given an entity target, a score name and (optionally) a scale value, retrieves the given scoreboard value for the entity target, multiplying it with the given scale value (if available). Created through ScoreboardValue#fromScoreboard.
  • minecraft:storage: A value from the command storage at a given nbt path. Created through new StorageValue.
  • minecraft:enchantment_level: A provider of values for each enchantment level. Created through EnchantmentLevelProvider#forEnchantmentLevel, providing a LevelBasedValue. Valid LevelBasedValues are:
    • Simply a constant value, without a specified type. Created through LevelBasedValue#constant.
    • minecraft:linear: A linearly-increasing value per enchantment level, plus an optional constant base value. Created through LevelBasedValue#perLevel.
    • minecraft:levels_squared: Squares the enchantment value, and then adds an optional base value to it. Created through new LevelBasedValue.LevelsSquared.
    • minecraft:fraction: Accepts two other LevelBasedValues, using them to create a fraction. Created through new LevelBasedValue.Fraction.
    • minecraft:clamped: Accepts another LevelBasedValue, alongside min and max values. Calculates the value using the other LevelBasedValue and clamps the result. Created through new LevelBasedValue.Clamped.
    • minecraft:lookup: Accepts a List<Float> and a fallback LevelBasedValue. Looks up the value to use in the list (level 1 is the first element in the list, level 2 is the second element, etc.), and uses the fallback value if the value for a level is missing. Created through LevelBasedValue#lookup.

Custom Number Providers

To create a custom number provider, implement the NumberProvider interface. For the sake of example, let's assume we want to create a number provider that changes the sign of the provided number:

// We accept another number provider as our base.
public record InvertedSignProvider(NumberProvider base) implements NumberProvider {
public static final MapCodec<InvertedSignProvider> CODEC = RecordCodecBuilder.mapCodec(inst -> inst.group(
NumberProviders.CODEC.fieldOf("base").forGetter(InvertedSignProvider::base)
).apply(inst, InvertedSignProvider::new));

// Return a float value. Use the context and the record parameters as needed.
@Override
public float getFloat(LootContext context) {
return -this.base.getFloat(context);
}

// Return an int value. Use the context and the record parameters as needed.
// Overriding this is optional, the default implementation will round the result of #getFloat.
@Override
public int getInt(LootContext context) {
return -this.base.getInt(context);
}

// Return a set of the loot context params used by this provider. See below for more information.
// Since we have a base value, we just defer to the base.
@Override
public Set<LootContextParam<?>> getReferencedContextParams() {
return this.base.getReferencedContextParams();
}
}

Like with custom loot entry types, we then use this codec in registration:

public static final DeferredRegister<LootNumberProviderType> LOOT_NUMBER_PROVIDER_TYPES =
DeferredRegister.create(Registries.LOOT_NUMBER_PROVIDER_TYPE, ExampleMod.MOD_ID);

public static final Supplier<LootNumberProviderType> INVERTED_SIGN =
LOOT_NUMBER_PROVIDER_TYPES.register("inverted_sign", () -> new LootNumberProviderType(InvertedSignProvider.CODEC));

And similarly, in our number provider class, we must override getType():

public record InvertedSignProvider(NumberProvider base) implements NumberProvider {
// other stuff here

@Override
public LootNumberProviderType getType() {
return INVERTED_SIGN.get();
}
}

Custom Level-Based Values

Custom LevelBasedValues can be created in a similar fashion by implementing the LevelBasedValue interface in a record. Again, for the sake of example, let's assume that we want to invert the output of another LevelBasedValue:

public record InvertedSignLevelBasedValue(LevelBasedValue base) implements LevelBaseValue {
public static final MapCodec<InvertedLevelBasedValue> CODEC = RecordCodecBuilder.mapCodec(inst -> inst.group(
LevelBasedValue.CODEC.fieldOf("base").forGetter(InvertedLevelBasedValue::base)
).apply(inst, InvertedLevelBasedValue::new));

// Perform our operation.
@Override
public float calculate(int level) {
return -this.base.calculate(level);
}

// Unlike NumberProviders, we don't return the registered type, instead we return the codec directly.
@Override
public MapCodec<InvertedLevelBasedValue> codec() {
return CODEC;
}
}

And again, we then use the codec in registration, though this time directly:

public static final DeferredRegister<MapCodec<? extends LevelBasedValue>> LEVEL_BASED_VALUES =
DeferredRegister.create(Registries.ENCHANTMENT_LEVEL_BASED_VALUE_TYPE, ExampleMod.MOD_ID);

public static final Supplier<MapCodec<? extends LevelBasedValue>> INVERTED_SIGN =
LEVEL_BASED_VALUES.register("inverted_sign", () -> InvertedSignLevelBasedValue.CODEC);

Loot Parameters

A loot parameter, known internally as a LootContextParam<T>, is a parameter provided to a loot table when rolled. They can be used by loot conditions and loot functions. For example, the minecraft:killed_by_player loot condition checks for the presence of the minecraft:player parameter.

Minecraft provides the following loot parameters:

  • minecraft:origin: A location associated with the loot table, e.g. the location of a loot chest. Access via LootContextParams.ORIGIN.
  • minecraft:tool: An item stack associated with the loot table, e.g. the item used to break a block. This is not necessarily a tool. Access via LootContextParams.TOOL.
  • minecraft:enchantment_level: An enchantment level, used by enchantment logic. Access via LootContextParams.ENCHANTMENT_LEVEL.
  • minecraft:enchantment_active: Whether the used item has an enchantment or not, used e.g. by silk touch checks. Access via LootContextParams.ENCHANTMENT_ACTIVE.
  • minecraft:block_state: A block state associated with the loot table, e.g. the broken block state. Access via LootContextParams.BLOCK_STATE.
  • minecraft:block_entity: A block entity associated with the loot table, e.g. the block entity associated with the broken block. Used e.g. by shulker boxes to save their inventory to the dropped item. Access via LootContextParams.BLOCK_ENTITY.
  • minecraft:explosion_radius: An explosion radius in the current context. Used primarily to apply explosion decay to drops. Access via LootContextParams.EXPLOSION_RADIUS.
  • minecraft:this_entity: An entity associated with the loot table, typically the killed entity. Access via LootContextParams.THIS_ENTITY.
  • minecraft:damage_source: A damage source associated with the loot table, typically the damage source that killed the entity. Access via LootContextParams.DAMAGE_SOURCE.
  • minecraft:attacking_entity: An attacking entity associated with the loot table, typically the killer of the entity. Access via LootContextParams.ATTACKING_ENTITY.
  • minecraft:direct_attacking_entity: A direct attacking entity associated with the loot table. For example, if the attacking entity were a skeleton, the direct attacking entity would be the arrow. Access via LootContextParams.DIRECT_ATTACKING_ENTITY.
  • minecraft:last_damage_player: A player associated with the loot table, typically the player that last attacked the killed entity, even if the player kill was indirect (for example: the player tapped the entity, and it was then killed by spikes). Used e.g. for player-kill-only drops. Access via LootContextParams.LAST_DAMAGE_PLAYER.

Custom loot parameters can be created by calling new LootContextParam<T> with the desired id. Since they are merely resource location wrappers, they do not need to be registered.

Loot Parameter Sets

Loot parameter sets, also known as loot table types and known as LootContextParamSets in code, are a collection of required and optional loot parameters. Despite their name, they are not Sets (not even Collections). Rather, they are a wrapper around two Set<LootContextParam<?>>s, one holding the required parameters (#getRequired) and one holding the required and optional parameters (#getAllowed). They are used to validate that users of loot parameters only use the parameters that can be expected to be available, and to verify that the required parameters are present when rolling a table. Besides that, they are also used in advancement and enchantment logic.

Vanilla provides the following loot parameter sets (required parameters are bold, optional parameters are in italics; the in-code names are constants in LootContextParamSets):

IDIn-code nameSpecified Loot ParametersUsage
minecraft:emptyEMPTYn/aFallback purposes.
minecraft:genericALL_PARAMSminecraft:origin, minecraft:tool, minecraft:block_state, minecraft:block_entity, minecraft:explosion_radius, minecraft:this_entity, minecraft:damage_source, minecraft:attacking_entity, minecraft:direct_attacking_entity, minecraft:last_damage_playerValidation.
minecraft:commandCOMMANDminecraft:origin, minecraft:this_entityCommands.
minecraft:selectorSELECTORminecraft:origin, minecraft:this_entityEntity selectors in commands.
minecraft:blockBLOCKminecraft:origin, minecraft:tool, minecraft:block_state, minecraft:block_entity, minecraft:explosion_radius, minecraft:this_entityBlock breaking.
minecraft:block_useBLOCK_USEminecraft:origin, minecraft:block_state, minecraft:this_entityNo vanilla uses.
minecraft:hit_blockHIT_BLOCKminecraft:origin, minecraft:enchantment_level, minecraft:block_state, minecraft:this_entityThe channeling enchantment.
minecraft:chestCHESTminecraft:origin, minecraft:this_entity, minecraft:attacking_entityLoot chests and similar containers, loot chest minecarts.
minecraft:archaeologyARCHAEOLOGYminecraft:origin, minecraft:this_entityArchaeology.
minecraft:vaultVAULTminecraft:origin, minecraft:this_entityTrial chamber vault rewards.
minecraft:entityENTITYminecraft:origin, minecraft:this_entity, minecraft:damage_source, minecraft:attacking_entity, minecraft:direct_attacking_entity, minecraft:last_damage_playerEntity kills.
minecraft:shearingSHEARINGminecraft:origin, minecraft:this_entityShearing entities, e.g. sheep.
minecraft:equipmentEQUIPMENTminecraft:origin, minecraft:this_entityEntity equipment for e.g. zombies.
minecraft:giftGIFTminecraft:origin, minecraft:this_entityRaid hero gifts.
minecraft:barterPIGLIN_BARTERminecraft:this_entityPiglin bartering.
minecraft:fishingFISHINGminecraft:origin, minecraft:tool, minecraft:this_entity, minecraft:attacking_entityFishing.
minecraft:enchanted_itemENCHANTED_ITEMminecraft:tool, minecraft:enchantment_levelSeveral enchantments.
minecraft:enchanted_entityENCHANTED_ENTITYminecraft:origin, minecraft:enchantment_level, minecraft:this_entitySeveral enchantments.
minecraft:enchanted_damageENCHANTED_DAMAGEminecraft:origin, minecraft:enchantment_level, minecraft:this_entity, minecraft:damage_source, minecraft:attacking_entity, minecraft:direct_attacking_entityDamage and protection enchantments.
minecraft:enchanted_locationENCHANTED_LOCATIONminecraft:origin, minecraft:enchantment_level, minecraft:enchantment_active, minecraft:this_entityFrost walker and soul speed enchantments.
minecraft:advancement_entityADVANCEMENT_ENTITYminecraft:origin, minecraft:this_entitySeveral advancement criteria.
minecraft:advancement_locationADVANCEMENT_LOCATIONminecraft:origin, minecraft:tool, minecraft:block_state, minecraft:this_entitySeveral advancement triggers.
minecraft:advancement_rewardADVANCEMENT_REWARDminecraft:origin, minecraft:this_entityAdvancement rewards.

Loot Context

The loot context is an object containing situational information for rolling loot tables. The information includes:

  • The ServerLevel the loot table is rolled in. Get via #getLevel.
  • The RandomSource used to roll the loot table. Get via #getRandom.
  • The loot parameters. Check presence using #hasParam, and get single parameters using #getParam.
  • The luck value, used for calculating bonus rolls and quality values. Usually populated via the entity's luck attribute. Get via #getLuck.
  • The dynamic drops consumers. See above for more information. Set via #addDynamicDrops. No getter available.

Loot Table

Combining all the previous elements, we finally get a loot table. Loot table JSONs can specify the following values:

  • pools: A list of loot pools.
  • neoforge:conditions: A list of data load conditions. Warning: These are data load conditions, not loot conditions!
  • functions: A list of loot functions to apply to all loot entry outputs of this loot table.
  • type: A loot parameter set, used to validate proper usage of loot parameters. Optional; if absent, validation will be skipped.
  • random_sequence: A random sequence for this loot table, in the form of a resource location. Random sequences are provided by the Level and used for consistent loot table rolls under identical conditions. This commonly uses the loot table's location.

An example loot table could have the following format:

{
"type": "chest", // loot parameter set
"neoforge:conditions": [
// data load conditions
],
"functions": [
// table-wide loot functions
],
"pools": [ // list of loot pools
{
"rolls": 1, // amount of rolls of the loot table, using 5 here will yield 5 results from the pool
"bonus_rolls": 0.5, // amount of bonus rolls
"name": "my_pool",
"conditions": [
// pool-wide loot conditions
],
"functions": [
// pool-wide loot functions
],
"entries": [ // list of loot table entries
{
"type": "minecraft:item", // loot entry type
"name": "minecraft:dirt", // type-specific properties, for example the name of the item
"weight": 3, // weight of an entry
"quality": 1, // quality of an entry
"conditions": [
// entry-wide loot conditions
],
"functions": [
// entry-wide loot functions
]
}
]
}
]
}

Rolling a Loot Table

To roll a loot table, we need two things: the loot table itself, and a loot context.

Let's start with getting the loot table itself. We can obtain a loot table using level.getServer().reloadableRegistries().getLootTable(lootTableId). As the loot data is only available through the server, this logic must run on a logical server, not a logical client.

tip

Minecraft's built-in loot table IDs can be found in the BuiltInLootTables class. Block loot tables can be obtained through BlockBehaviour#getLootTable, and entity loot tables can be obtained through EntityType#getDefaultLootTable or Entity#getLootTable.

Now that we have a loot table, let's build our parameter set. We begin by creating an instance of LootParams.Builder:

// Make sure that you are on a server, otherwise the cast will fail.
LootParams.Builder builder = new LootParams.Builder((ServerLevel) level);

We can then add loot context parameters, like so:

// Use whatever context parameters and values you need. Vanilla parameters can be found in LootContextParams.
builder.withParameter(LootContextParams.ORIGIN, position);
// This variant can accept null as the value, in which case an existing value for that parameter will be removed.
builder.withOptionalParameter(LootContextParams.ORIGIN, null);
// Add a dynamic drop.
builder.withDynamicDrop(ResourceLocation.fromNamespaceAndPath("examplemod", "example_dynamic_drop"), stack -> {
// some logic here
});
// Set our luck value. Assumes that a player is available. Contexts without a player should use 0 here.
builder.withLuck(player.getLuck());

Finally, we can create the LootParams from the builder and use them to roll the loot table:

// Specify a loot context param set here if you want.
LootParams params = builder.create(LootContextParamSet.EMPTY);
// Get the loot table.
LootTable table = level.getServer().reloadableRegistries().getLootTable(location);
// Actually roll the loot table.
List<ItemStack> list = table.getRandomItems(params);
// Use this instead if you are rolling the loot table for container contents, e.g. loot chests.
// This method takes care of properly splitting the loot items across the container.
List<ItemStack> containerList = table.fill(container, params, someSeed);
danger

LootTable additionally exposes a method named #getRandomItemsRaw. Unlike the various #getRandomItems variants, #getRandomItemsRaw method will not apply global loot modifiers. Use this method only if you know what you are doing.

Datagen

Loot tables can be datagenned by subclassing LootTableProvider and providing a list of LootTableSubProvider in the constructor:

public class MyLootTableProvider extends LootTableProvider {
// Get the PackOutput from GatherDataEvent.
public MyLootTableProvider(PackOutput output, CompletableFuture<HolderLookup.Provider> lookupProvider) {
super(output,
// A set of required table resource locations. These are later verified to be present.
// It is generally not recommended for mods to validate existence, therefore we pass in an empty set.
Set.of(),
// A list of sub provider entries. See below for what values to use here.
List.of(...));
}
}

Like all data providers, we register the provider to GatherDataEvent:

@SubscribeEvent
public static void onGatherData(GatherDataEvent event) {
CompletableFuture<HolderLookup.Provider> lookupProvider = event.getLookupProvider();
event.getGenerator().addProvider(
event.includeServer(),
output -> new MyLootTableProvider(output, lookupProvider)
);
}

LootTableSubProviders

LootTableSubProviders are where the actual generation happens. To get started, we implement LootTableSubProvider and override #generate:

public class MyLootTableSubProvider implements LootTableSubProvider {
// The parameter is provided by the lambda (see below). It can be stored and used to lookup other registry entries.
public MyLootTableSubProvider(HolderLookup.Provider lookupProvider) {
super(lookupProvider);
}

@Override
public void generate(BiConsumer<ResourceLocation, LootTable.Builder> consumer) {
// LootTable.lootTable() returns a loot table builder we can add loot tables to.
consumer.accept(ResourceLocation.fromNamespaceAndPath(ExampleMod.MOD_ID, "example_loot_table"), LootTable.lootTable()
// Add a loot table-level loot function. This example uses a number provider (see below).
.apply(SetItemCountFunction.setCount(ConstantValue.exactly(5)))
// Add a loot pool.
.withPool(LootPool.lootPool()
// Add a loot pool-level function, similar to above.
.apply(...)
// Add a loot pool-level condition. This example only rolls the pool if it is raining.
.when(WeatherCheck.weather().setRaining(true))
// Set the amount of rolls and bonus rolls, respectively.
// Both of these methods utilize a number provider.
.setRolls(UniformGenerator.between(5, 9))
.setBonusRolls(ConstantValue.exactly(1))
// Add a loot entry. This example returns an item loot entry. See below for more loot entries.
.add(LootItem.lootTableItem(Items.DIRT))
)
);
}
}

Once we have our loot table sub provider, we add it to the constructor of our loot provider, like so:

super(output, Set.of(), List.of(
new SubProviderEntry(
// A reference to the sub provider's constructor.
// This is a Function<HolderLookup.Provider, ? extends LootTableSubProvider>.
MyLootTableSubProvider::new,
// An associated loot context set. If you're unsure what to use, use empty.
LootContextParamSets.EMPTY
),
// other sub providers here (if applicable)
));

BlockLootSubProvider

BlockLootSubProvider is an abstract helper class containing many helpers for creating common block loot tables, e.g. single item drops (#createSingleItemTable), dropping the block the table is created for (#dropSelf), silk touch-only drops (#createSilkTouchOnlyTable), drops for slab-like blocks (#createSlabItemTable), and many more. Unfortunately, setting up a BlockLootSubProvider for modded usage involves more boilerplate:

public class MyBlockLootSubProvider extends BlockLootSubProvider {
// The constructor can be private if this class is an inner class of your loot table provider.
// The parameter is provided by the lambda in the LootTableProvider's constructor.
public MyBlockLootSubProvider(HolderLookup.Provider lookupProvider) {
// The first parameter is a set of blocks we are creating loot tables for. Instead of hardcoding,
// we use our block registry and just pass an empty set here.
// The second parameter is the feature flag set, this will be the default flags
// unless you are adding custom flags (which is beyond the scope of this article).
super(Set.of(), FeatureFlags.DEFAULT_FLAGS);
}

// The contents of this Iterable are used for validation.
// We return an Iterable over our block registry's values here.
@Override
protected Iterable<Block> getKnownBlocks() {
// The contents of our DeferredRegister.
return MyRegistries.BLOCK_REGISTRY.getEntries()
.stream()
// Cast to Block here, otherwise it will be a ? extends Block and Java will complain.
.map(e -> (Block) e.value())
.toList();
}

// Actually add our loot tables.
@Override
protected void generate() {
// Equivalent to calling add(MyBlocks.EXAMPLE_BLOCK.get(), createSingleItemTable(MyBlocks.EXAMPLE_BLOCK.get()));
dropSelf(MyBlocks.EXAMPLE_BLOCK.get());
// Add a table with a silk touch only loot table.
add(MyBlocks.EXAMPLE_SILK_TOUCHABLE_BLOCK.get(),
createSilkTouchOnlyTable(MyBlocks.EXAMPLE_SILK_TOUCHABLE_BLOCK.get()));
// other loot table additions here
}
}

We then add our sub provider to the loot table provider's constructor like any other sub provider:

super(output, Set.of(), List.of(new SubProviderEntry(
MyBlockLootTableSubProvider::new,
LootContextParamSets.BLOCK // it makes sense to use BLOCK here
)));

EntityLootSubProvider

Similar to BlockLootSubProvider, EntityLootSubProvider provides many helpers for entity loot table generation. Also similar to BlockLootSubProvider, we must provide a Stream<EntityType<?>> of entities known to the provider (instead of the Iterable<Block> used before). Overall, our implementation looks very similar to our BlockLootSubProvider, but with every mentioned of blocks swapped out for entity types:

public class MyEntityLootSubProvider extends EntityLootSubProvider {
public MyEntityLootSubProvider(HolderLookup.Provider lookupProvider) {
// Unlike with blocks, we do not provide a set of known entity types. Vanilla instead uses custom checks here.
super(FeatureFlags.DEFAULT_FLAGS, lookupProvider);
}

// This class uses a Stream instead of an Iterable, so we need to adjust this slightly.
@Override
protected Stream<EntityType<?>> getKnownEntityTypes() {
return MyRegistries.ENTITY_TYPES.getEntries()
.stream()
.map(e -> (EntityType<?>) e.value());
}

@Override
protected void generate() {
add(MyEntities.EXAMPLE_ENTITY.get(), LootTable.lootTable());
// other loot table additions here
}
}

And again, we then add our sub provider to the loot table provider's constructor:

super(output, Set.of(), List.of(new SubProviderEntry(
MyEntityLootTableSubProvider::new,
LootContextParamSets.ENTITY
)));