Loot Tables
Loot tables are JSON files that dictate what items should generate in various game situations. They control drops from mobs and blocks, contents of naturally generated containers (chests, barrels, dispensers), fishing rewards, archaeology brushing results, bartering exchanges, and more.
The loot table example of the playground runs in your browser and shows the JSON it generates.
Overview
Loot tables have several key characteristics:
- Context-dependent: Different loot contexts provide different parameters (killer entity, tool, luck level, etc.)
- Randomized: Use context providers for rolls and weighted entries for varied results
- Conditional: Apply predicates to pools, entries, and functions
- Composable: Reference other loot tables as entries for modularity
- Transformable: Apply item modifier functions to modify generated items
File Structure
Loot tables are stored as JSON files in data packs at:
For complete JSON specification, see the Minecraft Wiki - Loot table.
Creating Loot Tables
Use the lootTable builder function to create loot tables in Kore:
This generates data/my_datapack/loot_table/custom_chest.json.
In game: a chest filled from custom_chest, 3 rolls weighted 1:5:10, reroll to fill it again
Table Structure
A loot table consists of:
| Property | Type | Description |
|---|---|---|
type |
ContextKeySetArgument? |
Optional context type for validation |
pools |
List<LootPool> |
One or more pools that generate items |
modifier |
InlinableItemModifier? |
Optional item modifier applied to all drops |
randomSequence |
RandomSequenceArgument |
Optional deterministic random sequence |
Setting the Type
The type validates that the loot table uses appropriate context parameters:
The types are the game's context_key_set registry, generated as ContextKeySets and written as IDs ("type": "minecraft:entity"): ADVANCEMENT_ENTITY, ADVANCEMENT_LOCATION, ADVANCEMENT_REWARD, ARCHAEOLOGY, BARTER, BLOCK, BLOCK_INTERACT, BLOCK_USE, CHEST, COMMAND, COMMAND_COMPUTE_DEFAULT, COMMAND_COMPUTE_ENTITY, COMMAND_COMPUTE_POSITION, COMMAND_SLOT_SOURCE, CONTAINER_PROCESS, EMPTY, ENCHANTED_DAMAGE, ENCHANTED_ENTITY, ENCHANTED_ITEM, ENCHANTED_LOCATION, ENTITY, ENTITY_INTERACT, EQUIPMENT, FISHING, GENERIC, GIFT, HIT_BLOCK, SELECTOR, SHEARING, VAULT, VILLAGER_TRADE.
The types used by enchantment effects, by container processing and by the compute command provide these parameters:
| Type | Parameters |
|---|---|
COMMAND_COMPUTE_DEFAULT |
origin, optionally this entity |
COMMAND_COMPUTE_ENTITY |
origin, optionally this entity, target entity |
COMMAND_COMPUTE_POSITION |
origin, optionally this entity, block entity, block state |
CONTAINER_PROCESS |
block entity, block state, container, origin |
ENCHANTED_DAMAGE |
this entity, enchantment level, origin, damage source, optionally the direct attacker and the attacker |
ENCHANTED_ENTITY |
this entity, enchantment level, origin |
ENCHANTED_ITEM |
tool, enchantment level |
ENCHANTED_LOCATION |
this entity, enchantment level, origin, whether the enchantment is active |
HIT_BLOCK |
this entity, enchantment level, origin, block state |
Global Modifier
Apply an item modifier to all items dropped by the table, one function being written bare and several ones as a list:
Each modifier (table, pool and entry) also takes the ID of an item modifier file or an item modifier tag, written as a bare string:
Pools
Each pool represents an independent set of rolls. A table can have multiple pools that all contribute to the final loot.
Pool Properties
| Property | Type | Description |
|---|---|---|
rolls |
ContextIntProvider |
How many times to select from entries |
bonusRolls |
ContextFloatProvider? |
Additional rolls per luck level |
condition |
PredicateCondition? |
Condition that must pass for the pool to activate, several are AND-ed |
entries |
List<LootEntry> |
Possible entries to select from |
modifier |
InlinableItemModifier? |
Item modifier applied to items from this pool |
Basic Pool
A pool with only a number of rolls takes it as its first argument, pool(2) { ... } writing "rolls": 2. pool { } rolls once.
Conditional Pool
Pool with a Modifier
Context Providers
Context providers determine dynamic numeric values for rolls, counts, and other quantities. The game splits them in two families by the number they produce, and each field takes exactly one of them:
| Family | Kotlin type | Number | Constant | Fields |
|---|---|---|---|---|
| int | ContextIntProvider |
32-bit integer | 5 |
rolls, item function counts, levels and durations, trade maxUses and xp, trade set amount, intValueCheck, timeCheck and entityScores ranges |
| float | ContextFloatProvider |
32-bit float | 5.0 |
bonusRolls, setDamage, attribute amounts, randomChance, trade reputationDiscount, floatValueCheck |
The builders are extensions on NumberProviderScope, so they only resolve inside the blocks that accept a context provider: loot tables and pools, item modifiers and their functions, predicates, villager trades and trade sets. The parameter type picks the family: constant(5) and uniform(1, 5) build int providers, constant(5f) and uniform(1f, 5f) float ones. A builder that has the same parameters in both families has one name per family (intStorage and floatStorage, for instance).
Most fields take a plain number for a fixed value instead of constant(...): rolls(3), bonusRolls(0.5f), setCount(2), enchantWithLevels(levels = 30) and potionEffect(Effects.NIGHT_VISION, 200) write the bare number. constant(...) stays for a fixed value inside an aggregate (add, max, min, mul) and for a single bound such as limitCount(max = constant(64)). The fallback of score, intStorage and floatStorage takes a plain number too, score("kills", EntityTarget.THIS, 0) writing "fallback": 0.
A range given where one number is expected is a uniform draw with both ends included: rolls(1..3), pool(1..3) { }, setCount(1..3), enchantWithLevels(levels = 20..39), maxUses(8..12) write {"type": "minecraft:uniform", "min": 1, "max": 3}, and a float range such as bonusRolls(0f..1f) or setDamage(0.2f..0.8f) does the same with floats, the game never returning the end itself. Equal ends write the number (rolls(2..2) is 2) and an empty range such as 3..1 throws an IllegalArgumentException. A range given where a range is expected is the bounds it clamps or matches and draws nothing: limitCount(1..32), intValueCheck(score, 0..10) and timeCheck(clock, 0..12000).
| Builder | Family | Description | Example |
|---|---|---|---|
abs(input) |
int, float | Absolute value | abs(sub(constant(1), constant(5))) |
add(...) |
int, float | Sum of several providers, or of a tag of providers | add(constant(1), uniform(1, 3)) |
avg(...) |
int, float | Mean of several providers, or of a tag of providers | avg(constant(2), constant(4)) |
binomial(n, p) |
int | Binomial distribution, n an int and p a float provider |
binomial(5, 0.5f) |
ceil(input), floor(input), round(input), truncate(input) |
float | Rounds to a whole number | round(uniform(0.5f, 2.5f)) |
conditional(...) |
int, float | One value when a condition passes, another otherwise | conditional(5, 2) { killedByPlayer() } |
constant(value) |
int, float | Fixed value | constant(5), constant(5f) |
cos(input), sin(input) |
float | Cosine and sine, in radians | sin(constant(1f)) |
div(left, right) |
int, float | Division, an int one rounds toward zero | div(constant(7), constant(2)) |
enchantmentLevel(...) |
float | Based on enchantment level (requires enchantment context) | enchantmentLevel(5) |
intEnvironmentAttribute(attribute), floatEnvironmentAttribute(attribute) |
int, float | Current value of a numeric env attribute | floatEnvironmentAttribute(EnvironmentAttributes.Visual.FOG_START_DISTANCE) |
floorDiv(left, right), floorMod(left, right) |
int | Division rounded down, and its remainder | floorMod(score("kills", EntityTarget.THIS), constant(10)) |
fromFloat(input) |
int | Float provider converted to an int, truncated toward zero | fromFloat(constant(2.7f)) |
fromInt(input) |
float | Int provider converted to a float | fromInt(score("kills", EntityTarget.THIS)) |
intNumberDispatcher(...) { }, floatNumberDispatcher(...) { } |
int, float | Value of the first matching case, or a default | intNumberDispatcher(default = 0) { case(1) { killedByPlayer() } } |
intStorage(storage, path), floatStorage(storage, path) |
int, float | Value from command storage, with an optional fallback |
intStorage("my_pack:data", "player.health", 0) |
intWeightedList { }, floatWeightedList { } |
int, float | Value of one entry, picked by weight | intWeightedList { entry(30, 1); entry(70, 0) } |
length(...) |
float | Euclidean length of a vector, or of a tag of providers | length(constant(3f), constant(4f)) |
max(...) |
int, float | Largest of several providers, or of a tag of providers | max(constant(1), score("kills", EntityTarget.THIS)) |
min(...) |
int, float | Smallest of several providers, or of a tag of providers | min(constant(5), score("kills", EntityTarget.THIS)) |
mod(left, right) |
int, float | Remainder of a division | mod(constant(7), constant(2)) |
mul(...) |
int, float | Product of several providers, or of a tag of providers | mul(reference(ContextIntProviders.Cooking.TIME_COAL), constant(2)) |
negate(input) |
int, float | Opposite value | negate(constant(1)) |
pow(base, exponent) |
int, float | Power | pow(constant(2), constant(3)) |
reference(id) |
int, float | Context provider file, written as its ID | reference(ContextIntProviders.Cooking.TIME_COAL) |
score(score, target) |
int | Value from a scoreboard objective, with an optional fallback |
score("kills", EntityTarget.THIS, 0) |
sqrt(input) |
float | Square root | sqrt(constant(4f)) |
sub(left, right) |
int, float | Difference | sub(constant(5), score("deaths", EntityTarget.THIS)) |
uniform(min, max) |
int, float | Random value between min and max | uniform(1, 5), uniform(0.5f, 1f) |
Only the int family has binomial, floorDiv, floorMod, fromFloat and score, only the float family has ceil, cos, enchantmentLevel, floor, fromInt, length, round, sin, sqrt and truncate. score has no float form because the game only reads an int from a scoreboard: wrap it in fromInt(...) to use it in a float field.
conditional, the number dispatchers and the weighted lists nest other providers, and the conditions are the ones of predicates. A condition is either passed as a value (conditional(SurvivesExplosion, 1, 0)), as a predicate ID (conditional(fastCooking, 800, 1600)) or declared in a block holding exactly one condition. onFalse and default are 0 when omitted. A dispatcher case is written { "condition": ..., "value": ... }.
A constant is the bare number (5 for an int provider, 5.0 for a float one), and every other provider is an object that always carries its type: the game rejects {"min": 1, "max": 2}, it has to be {"type": "minecraft:uniform", "min": 1, "max": 2}.
intEnvironmentAttribute reads an attribute holding an integer, and no vanilla attribute is representable as one. Wrap the float one in fromFloat(...) to read it as an int:
Context provider IDs
Wherever a context provider is expected (rolls, min, max, on_true, the item modifier counts...), the game also accepts the ID of a context provider file of the same family, written as a bare string. reference(id) builds it, from a ContextIntProviderArgument for an int provider and a ContextFloatProviderArgument for a float one, and contextIntProvider(...) and contextFloatProvider(...) return the ID to pass:
The inputs of add, avg, max, min and mul (and of length for float providers) take one provider or ID, a list mixing IDs and inline providers, or a whole #tag of providers of the family: add(ContextIntProviderTagArgument("bonuses", "my_pack")) writes "inputs": "#my_pack:bonuses". A tag is only valid as the whole inputs, not as one element of a list: the game reads inputs as a set of providers, and Kore throws an IllegalArgumentException when a list of several elements holds a tag. The list can't be empty either: the game reports an aggregate without any input, and Kore throws.
min caps a value, here the roll count at 5 whatever the score is:
A provider that references itself, directly or through other files, makes the pack fail to load.
Expressions
Kotlin operators build the same providers as the builders, so an expression reads like the maths it computes. The right operand is a provider or a number of the same family (an Int for an int provider, a Float for a float one):
| Expression | Family | Builds |
|---|---|---|
a + b |
int, float | add |
a - b |
int, float | sub |
a * b |
int, float | mul |
a / b |
int, float | div, an int one rounds toward zero |
a % b |
int, float | mod, with the sign of a for an int and of b for a float |
-a |
int, float | negate |
a floorDiv b, a floorMod b |
int | floor_div, floor_mod |
a pow b |
int, float | pow |
a.toFloat(), a.toInt() |
int, float | from_int, from_float |
a.coerceIn(0, 10) |
int, float | max([min([a, 10]), 0]), with coerceAtLeast(0) and coerceAtMost(10) |
min(a, 5), max(a, 5) |
int, float | min and max with a constant |
A number can lead an expression inside the blocks that accept providers (2 + score(...), 1.5f * speed), and nowhere else. The two families don't mix, convert with toFloat() or toInt():
The operators keep the tree small, and only on what they build (a builder like add(...) stays as written):
- Int sums and products become one
addormul:score + 1 + 2isadd([score, 3]), and(score + 1) + (deaths + 2)isadd([score, deaths, 3]). The constants of the list are merged into one, put last. The game adds all the inputs on 64 bits and checks the result once, so a chain only fails when its final total overflows. A merged constant that doesn't fit an int isn't merged:score + Int.MAX_VALUE + 1staysadd([score, 2147483647, 1]). A+ 0and a* 1disappear, and two constants fold into one (constant(2) + constant(3)is5). - Int subtraction and negation fold two constants when the result fits an int (
constant(5) - constant(3)is2,-constant(5)is-5).-constant(Int.MIN_VALUE)and an overflowing difference stay as providers, since the game fails on them. - Int
/,%,floorDiv,floorModandpoware never folded, soscore % 0still reaches the game. - Float expressions flatten the left operand only:
(a + b) + cis oneadd,a + (b + c)keeps its nesting, because float sums aren't associative. Constants are never folded, since that could change the rounding. A+ 0fand a* 1fdisappear (dropping+ 0fcan keep a-0.0that the game, whose floataddstarts from0.0f, would turn into0.0). - An
addormulmade of a#tagis never merged into a bigger list: the game only accepts a tag as the wholeinputs.
<kills> stands for the score provider of kills. toFloat() moves an expression to float maths, and toInt() brings it back:
Evaluation
- Outside commands, a provider that hits an error (a division by zero, an int overflow) or gives Infinity or NaN returns
0. Thecomputecommand fails instead, anddata modify ... computestores0. - Int
addandmulare computed on 64 bits and fail when the result doesn't fit in 32 bits.avgcan't overflow. fromFloattruncates toward zero (-2.7gives-2) and fails when the float isn't finite or doesn't fit in an int.uniformreturnsminwhenminis at or abovemax.- Float providers are 32-bit floats: whole numbers are exact up to 16,777,216 (2²⁴), past it neighboring values read as the same one. Int providers are exact 32-bit integers, so
scorein an int field and the ranges ofentityScoresstay exact on the whole range of a score.fromInt(score(...))is a float again and loses the same precision past 2²⁴.
Examples
This writes weighted_list as {"type": "minecraft:weighted_list", "distribution": [{"data": 1.0, "weight": 30}, {"data": 0.0, "weight": 70}]}.
Context provider files
A context provider can also live in its own file, data/<namespace>/context_int_provider/<fileName>.json for an int provider and data/<namespace>/context_float_provider/<fileName>.json for a float one, so the game components that take a context provider by ID can point at it. contextIntProvider and contextFloatProvider take a block returning the provider and give back the reference to use:
A constant is written as a bare number, the way vanilla's compostable/always_add_one.json is:
The vanilla files are listed in ContextIntProviders and ContextFloatProviders, e.g. ContextIntProviders.Compostable.LOW for minecraft:compostable/low. Context providers, including the ones of other provider files, can reference a file of their family by its ID with reference(id), see Context provider IDs. The item components cooking_fuel, brewing_fuel and compostable take a plain number or such an ID, see Components. A file itself can't be a bare reference because the game reads it as an inline provider, so Kore throws when generating one.
Entries
Entries define what can be selected during a pool roll. There are singleton entries (yield items) and composite entries (combine other entries).
Singleton Entries
Item Entry
Drops a specific item:
The entry's condition and modifier live in the features.loottables.entries package, the pool's and table's in features.loottables. The loot table, item modifier and predicate builders are DSL-scoped, so calling a builder that only an enclosing pool or table has fails to compile rather than silently adding to that pool: import the entry's version when the compiler reports it.
Loot Table Entry
Drops the items of other loot tables, given by ID or by loot table tag. The entry's value is one ID, a list of IDs or a tag:
With several loot tables, expand = true picks one of them at random, while the default drops all of them. A loot table tag is written #my_pack:my_tag:
The game also accepts a loot table written in place of an ID, which isn't possible in Kore because value only takes loot table IDs and tags. Declare the table with lootTable("...") and point at it.
Tag Entry
Drops items from an item tag, or from a list of items. items is written as a bare string when it holds one element:
expand = true picks one item at random, while the default drops all of them.
Dynamic Entry
For block-specific drops (decorated pot sherds or shulkers contents):
Empty Entry
A weighted entry that drops nothing (useful for rarity):
Composite Entries
Alternatives
Selects the first entry whose condition passes:
Group
All children are added to the pool if the condition passes. Composite entries take a modifier like singleton entries:
Sequence
Children are added until one fails its condition:
Slots Entry
Selects items from inventory slots specified by a slot source:
Entry Properties
Singleton entries share these properties:
| Property | Type | Description |
|---|---|---|
weight |
Int |
Selection weight (higher = more likely) |
quality |
Int |
Modifies weight based on luck: weight + quality × luck |
condition |
PredicateCondition? |
Entry only available if the condition passes |
modifier |
InlinableItemModifier? |
Item modifier applied to this entry's items |
alternatives, group and sequence entries have condition and modifier too. loot_table and tag entries also take expand (Boolean?): true picks one loot table or item at random, the default drops all of them.
Conditions (Predicates)
A pool or an entry holds one condition. Several conditions declared in its block are wrapped in an all_of, the same way as in a predicate file.
A pool or an entry can also point at a predicate file by ID, written as the bare ID string:
See Predicates for the complete list of available conditions.
Modifiers (Item Modifiers)
A modifier transforms the generated items. It can be set at table, pool, or entry level.
Common Functions
A single function is written as an object, several ones as a list. See Item Modifiers for the complete list of available functions.
Full Example
Generated JSON
Using with Commands
Spawn loot from a table using the /loot command. The surrounding command builders are documented in Commands, while reusable conditions usually come from Predicates:
Overriding Vanilla Tables
To modify vanilla loot tables, create a file with the same path in your datapack. You can combine that with Item Modifiers when you want reusable post-processing logic instead of duplicating functions inside every pool:
Best Practices
- Use appropriate types - Set the
typefield to catch invalid context parameter usage early - Organize with multiple pools - Use separate pools for different drop categories (guaranteed, rare, conditional)
- Leverage composition - Reference other loot tables instead of duplicating entries
- Weight appropriately - Use meaningful weights (e.g., 1/5/10/50) for clear rarity tiers
- Apply conditions at the right level - Pool conditions for entire categories, entry conditions for specific items
See Also
- Item Modifiers - Functions applied to loot table items
- Advancements - Rewards can reference loot tables
- Tags - Use item tags for tag entries
External Resources
- Minecraft Wiki: Loot table - Official JSON format reference
- Minecraft Wiki: Loot context - Understanding loot contexts
