Chapter 37 — Advanced Loot Tables
What You’ll Build
Back in Chapters 16 and 17 you learned the everyday half of loot tables: pools that get rolled, entries with weights that decide which item wins a roll, conditions that gate a pool or entry, and functions that modify the dropped item. That’s enough to make a zombie drop a custom sword or a chest fill with random treasure.
This chapter adds the advanced half: the parts that let a single loot table make real decisions.
By the end you’ll have a custom block in mypack that drops different things depending on the
enchantments on the tool that mined it: bare-handed or with a plain pickaxe you get a chunk of
raw material, with Fortune you get more of it, and with a special enchantment you get a rare
shard that copies data straight off the block. Along the way you’ll learn entry types that choose
between children, functions that copy data off the thing being looted, a function that calls a
saved item modifier by name, the full list of loot context types (what data each situation hands
your loot table), a condition that reads a scoreboard, and the tools that scale drops by
enchantment level.
This chapter extends your mypack pack from Chapter 9 and the test world from Chapter 1. It assumes
the loot-table structure from Chapter 16 (pools, entries, weight, number providers), the
conditions and functions from Chapter 17 (match_tool, set_name, set_components,
enchanted_count_increase), predicates from Chapter 18 (all_of/any_of/inverted and the
reference condition), data components from Chapter 21, and scoreboards from Chapter 11. Every
new keyword below is copied straight from the wiki so the spelling is exactly right.
Composite entries: choosing between children
In Chapter 16 every entry you wrote was a singleton entry: one entry, one weighted chance to
drop one kind of item (item, tag, loot_table, empty). The other kind is a
composite entry: an entry that doesn’t get rolled itself, but instead expands into child
entries before the pool is rolled. Composite entries first expand their
children, and then singleton entries that do not meet their conditions are removed from the pool,
and the entries are rolled afterward.
A composite entry has a children field (the list of entries it expands into) instead of a
name. There are three composite types:
group— “All entries in thechildrenare extracted.” A plain bundle. Handy when one condition should apply to several entries at once: put the condition on thegroupand it gates the whole batch.alternatives— “Extracts only the first successful (conditions are met) entry. Conditions are checked in order.” This is an if / else-if / else for loot: the first child whose conditions pass wins, and the rest are skipped.sequence— “Extracts the child entries in sequential order, continuing until an entry’s conditions fail, then no more children are extracted.” This drops a run of children: every one from the top until the first failure.
Here’s the shape of an alternatives entry. Read it as “drop a diamond if a player’s tool has Silk
Touch; otherwise drop coal”:
{
"type": "minecraft:alternatives",
"children": [
{
"type": "minecraft:item",
"name": "minecraft:diamond",
"conditions": [
{
"condition": "minecraft:match_tool",
"predicate": { "enchantments": [ { "enchantments": "minecraft:silk_touch" } ] }
}
]
},
{
"type": "minecraft:item",
"name": "minecraft:coal"
}
]
}
The first child has a condition; the second has none, so it always passes. alternatives checks
them top to bottom and keeps the first that passes, exactly the “else” behaviour you’d want.
Under the Hood (skippable). Why is a composite entry not just rolled like a normal entry? Because weights only make sense between things competing in the same pool. A composite entry flattens into the pool first (its surviving children become ordinary weighted singletons), and then the pool rolls. So
alternativesdecides which children exist, andweightstill decides which of the survivors wins the roll.
dynamic and tag, revisited
Two more entry types round out the advanced set. You met tag briefly in Chapter 16; the other,
dynamic, is new here.
The dynamic entry “Drops block-specific loot.” Its name field can be contents (which
drops the items in a shulker box) or sherds (which drops the sherds of a decorated pot). It
“Does not work for other blocks,” so it’s a narrow tool: it exists so a
shulker box drops what’s inside it and a decorated pot drops its sherds.
{
"type": "minecraft:dynamic",
"name": "contents"
}
The tag entry drops items from an item tag. Its behaviour flips on a boolean expand field:
if expand is false it’s a single entry that “drops all items in the tag”; if expand is true
it becomes a composite entry that “provides one singleton entry per item in the tag with the same
weight and conditions.” It spreads the tag’s items across the pool as separate weighted
options. (One caveat worth knowing: when expand is true, item modifiers attached to it “do
not work due to a bug.”)
Reusing whole loot tables and saved modifiers
You already have two ways to reuse loot you built elsewhere; this section just lines them up.
The loot_table entry (Chapter 16) drops the loot from another loot table. Its field is value,
the loot table to be used. The wiki warns: it “Cannot be the ID of the current loot table file.
Recursive calling is not allowed.” So a chest table can pull in a shared “common junk” table
without copy-pasting it.
{
"type": "minecraft:loot_table",
"value": "mypack:common_junk"
}
The matching tool on the function side is the reference function. From the Item modifier
page: reference — “Call sub-functions,” with a name field that is the “Location of function to
call.” In plain terms, you save an item modifier as its own file (you learned standalone item
modifiers in Chapter 20) and then call it by name from inside a loot table’s functions list,
instead of pasting the whole modifier in. One saved modifier, reused everywhere.
{
"function": "minecraft:reference",
"name": "mypack:name_mystic_shard"
}
Modern Minecraft. Don’t confuse this
referencefunction with thereferencecondition from Chapter 18. They share a name and anamefield but do different jobs: the condition (in aconditionslist) invokes a saved predicate and returns pass/fail; the function (in afunctionslist) invokes a saved item modifier and applies it to the item. Same idea (“call something I saved elsewhere”) applied to two different kinds of saved file.
Copy functions: pulling data off the source
Sometimes the item you drop should carry data from the thing it came from: the block that broke, the mob that died. Two functions do this.
copy_components — “Copies components from a specified source onto an item.” Its fields are:
source— “Source type to pull from. Specifies an entity or block entity from loot context.” The wiki lists the allowed values:block_entity,this,attacker,direct_attacker,attacking_player,target_entity,interacting_entity,tool.include— optional, “A list of components to include. If omitted, all components are copied.”exclude— optional, “A list of components to exclude.”
So copy_components with "source": "block_entity" copies data components off the broken block
entity onto the dropped item. That’s how, for example, a broken block can drop an item that
remembers a stored component.
{
"function": "minecraft:copy_components",
"source": "block_entity",
"include": [ "minecraft:custom_name" ]
}
The second is copy_custom_data. The wiki describes it as: “Copies NBT values from an entity,
block entity, or storage to the item’s minecraft:custom_data component.” This is the modern,
correctly-named function for copying raw NBT. If you’ve seen old tutorials call it copy_nbt, this
is its current name. Its source (shorthand form) names the thing to copy from, and an ops list
gives the copy operations, each with a source NBT path, a target path (relative to the item’s
minecraft:custom_data), and an op set to replace, append, or merge.
{
"function": "minecraft:copy_custom_data",
"source": "block_entity",
"ops": [
{ "source": "Owner", "target": "miner", "op": "replace" }
]
}
Modern Minecraft. In Chapter 21 you learned that data components replaced most raw NBT. That’s why there are two copy functions.
copy_componentsis the modern, component-aware one you’ll reach for most;copy_custom_datais for the cases where you genuinely need to move loose NBT into the catch-allminecraft:custom_datacomponent. Prefercopy_componentsunless you specifically need raw NBT.
Loot context types: what data each situation gives you
In Chapter 17 you met loot context as the reason killed_by_player and match_tool only work
on mob and block tables: a loot table only has the parameters its situation supplies. This section
deepens that. Loot context is “a set of parameters available to loot tables,
predicates, item modifiers, and number providers,” and the check is done “when the data
pack is loaded, rather than at runtime.”
That check is driven by the loot table’s type field (Chapter 16’s root field). The wiki: type
“Specifies the loot context in which the loot table should be invoked. All item modifiers,
predicates and number providers are then validated to ensure the parameters of the context type
specified here cover all requirements, and prints a warning message in the output log if any
modifier or predicate requires a context parameter that is not covered.” In other words, declaring
the right type lets the game catch your mistakes at load time. If you ask for the killer in a
chest table, you get a warning, not a silent failure later.
Different situations provide different parameters. Three you’ll care about most:
- A chest / container being opened provides an Origin (the centre of the chest) and a
thisentity (the entity that opened it). No tool, no killer. - A living entity’s death (a mob dying) provides the
thisentity (the one that died), an Origin, a Damage source, and three attacker entities:attacker(“the source of the final damage”),direct_attacker(“the entity that directly contacted the victim”), andattacking_player(“the player that most recently damaged the victim”). This is whykilled_by_player, which checks for thatattacking_player, works on mob tables. - Mining a block provides a Block state (“the block that was broken”), an Origin, a
Tool (“the tool used to mine the block”), a
thisentity (the player who mined it), and a Block entity (“any block entity data of the block that was broken, if it was a block entity”). This is the context your custom-block table runs in, and the reasonmatch_tooland Fortune scaling are available to it.
Two special type values are worth naming. "type": "empty" “means no context parameters can be
used in this loot table”; it’s the do-nothing table. "type": "generic" (the default if you omit
type) “means no checking for context parameters in this loot table when loading the data pack,” so
it skips the load-time check entirely. Declaring a specific type is better when you can, precisely
because it turns on that helpful warning.
Try It! Make a deliberate mistake to see the safety net work. In a block-mining table, add a
killed_by_playercondition (which needs theattacking_playerthe mob-death context provides, not the block context). With the table’stypeset correctly for block mining, the game should warn you in the output log when the pack loads, before you ever break the block.
entity_scores: reading a scoreboard in a loot condition
Chapter 17’s conditions tested the world (was it a player kill? what tool?). The entity_scores
condition reaches into the scoreboard system from Chapter 11. The wiki: it “Checks the scoreboard
scores of an entity.” Its fields:
entity— “The entity to check. Specifies an entity from loot context” (sothis,attacker, and so on — only the entities the current context provides).scores— “Scores to check. All specified scores must pass for the condition to pass.” Each key is a scoreboard objective; the value is either a{ "min": ..., "max": ... }range or, in the shorthand form, “a single number” the score must equal.
So you can make a drop happen only when a player’s score is in range. For example, only reward a block’s special loot once the player has reached a quest milestone you track on a scoreboard objective.
{
"condition": "minecraft:entity_scores",
"entity": "this",
"scores": {
"mypack_quest": { "min": 5 }
}
}
This reads: pass only if the this entity’s mypack_quest objective is at least 5. Because it
needs the entity from loot context, it “always fails if not provided,” so the
context’s type has to actually supply that entity.
Scaling drops by enchantment: table_bonus and apply_bonus
The last pieces are the tools that make drops respond to Fortune and similar enchantments: the way mining gravel with Fortune drops flint more often, or an ore drops more ingots. There are two, and they do different jobs.
table_bonus is a condition. The wiki: it “Passes with probability picked from a list,
indexed by enchantment power. Requires tool provided by loot context. If not provided, the
enchantment level is regarded as 0.” Fields:
enchantment— the resource location of the enchantment.chances— “List of probabilities for enchantment power, indexed from 0.”
So chances: [0.1, 0.5, 1.0] means: with the enchantment at level 0 the entry passes 10% of the
time, at level 1 it’s 50%, at level 2 it’s guaranteed. It’s a chance gate that improves with the
enchantment, perfect for a rare extra drop.
{
"condition": "minecraft:table_bonus",
"enchantment": "minecraft:fortune",
"chances": [ 0.1, 0.14, 0.25, 1.0 ]
}
apply_bonus is a function: it scales the item count. The wiki: it “Applies a predefined
bonus formula to the count of the item stack.” Fields:
enchantment— the enchantment “used for level calculation.”formula— a resource location. The wiki lists three:ore_drops(“a special function used for ore drops in the vanilla game”),uniform_bonus_count(uniform distribution from 0 tolevel * bonusMultiplier), andbinomial_with_bonus_count.parameters— “Values required for the formula” (e.g.bonusMultiplierforuniform_bonus_count;extraandprobabilityfor the binomial one).
{
"function": "minecraft:apply_bonus",
"enchantment": "minecraft:fortune",
"formula": "minecraft:ore_drops"
}
That single function is exactly how vanilla ores multiply their drops with Fortune. Drop it into your own block’s loot table and your block behaves like an ore.
Modern Minecraft. Chapter 17 taught
enchanted_count_increasefor the Looting bonus on mob drops.apply_bonusis its cousin for mining: same idea (more drops per enchantment level), but it runs in the block-mining context and reads the tool’s enchantment instead of the killer’s. Useenchanted_count_increasefor mob loot, andapply_bonusfor block loot.
Walkthrough: a tool-aware custom block drop
Time to put it together. You’ll write a loot table for a custom “mystic ore” block. The plan:
- If the tool has Silk Touch, drop the block itself (one mystic ore).
- Otherwise, drop a raw material, and if the tool has Fortune, drop more of it (via
apply_bonus), plus a chance at a rare bonus shard (viatable_bonus). - The rare shard gets a custom name applied through a saved item modifier called with
reference.
First, the saved modifier the table will call. Create this file:
mypack/data/mypack/item_modifier/name_mystic_shard.json
{
"function": "minecraft:set_name",
"name": { "text": "Mystic Shard", "italic": false, "color": "aqua" },
"target": "custom_name"
}
This is a standalone item modifier (Chapter 20): a single set_name function that gives the item
the custom name “Mystic Shard” (Chapter 17 taught set_name with its target of custom_name).
Now the loot table itself. Notice the type at the top: minecraft:block declares the
block-mining context, which turns on load-time checking and gives us the tool parameter that
match_tool, table_bonus, and apply_bonus all need.
mypack/data/mypack/loot_table/blocks/mystic_ore.json
{
"type": "minecraft:block",
"pools": [
{
"rolls": 1,
"entries": [
{
"type": "minecraft:alternatives",
"children": [
{
"type": "minecraft:item",
"name": "minecraft:diamond_ore",
"conditions": [
{
"condition": "minecraft:match_tool",
"predicate": {
"enchantments": [ { "enchantments": "minecraft:silk_touch" } ]
}
}
]
},
{
"type": "minecraft:item",
"name": "minecraft:diamond",
"functions": [
{
"function": "minecraft:apply_bonus",
"enchantment": "minecraft:fortune",
"formula": "minecraft:ore_drops"
}
]
}
]
}
]
},
{
"rolls": 1,
"conditions": [
{
"condition": "minecraft:table_bonus",
"enchantment": "minecraft:fortune",
"chances": [ 0.0, 0.25, 0.5, 1.0 ]
}
],
"entries": [
{
"type": "minecraft:item",
"name": "minecraft:amethyst_shard",
"functions": [
{
"function": "minecraft:reference",
"name": "mypack:name_mystic_shard"
}
]
}
]
}
]
}
Walk through what happens when the block breaks:
- First pool,
alternativesentry. Its first child dropsdiamond_oreonly ifmatch_toolsees Silk Touch. If Silk Touch is present, that child wins and the second is skipped, so you get the ore block back. If not, the second child (no condition, always passes) wins and drops adiamond, whose count is scaled byapply_bonusreading Fortune, so plain tools give one and Fortune gives more. - Second pool only runs at all when its
table_bonuscondition passes, and that chance climbs with Fortune (0.0at level 0 means never without Fortune;1.0at level 3 means always). When it passes, it drops anamethyst_shardand calls your savedname_mystic_shardmodifier throughreference, so the shard arrives already named “Mystic Shard.”
To use this table on an actual block in-game you’d assign it the way Chapter 17 assigned a table to
zombies, by overriding the block’s loot table file (here we’ve put it at a mypack: path so you can
test it with the loot command first). Try it from a function:
mypack/data/mypack/function/test_mystic_ore.mcfunction
loot give @s loot mypack:blocks/mystic_ore
Figure (to be captured). running test_mystic_ore with a Fortune III pickaxe in hand and seeing extra diamonds plus a named Mystic Shard appear
Because loot give ... loot uses the chest/command context rather than a real block break, the
surest test is to bind the table to a real block and mine it. But the command is a quick first
check that the file parses and the named shard appears.
Practice
These extend the block you just built. Keep working in the same file unless told otherwise.
-
A coal consolation prize. Add a third child to the
alternativesentry (after the Silk Touch and Fortune children) so there’s always something. Sincealternativeskeeps the first passing child, where in thechildrenlist must a no-condition fallback go for it to act as the “else”? -
Score-gated jackpot. Add a third pool that drops a
minecraft:nether_star, gated by anentity_scorescondition on thethisentity requiring an objectivemypack_mining_levelof at least 10. (You set up scoreboard objectives in Chapter 11.) Rememberentity_scoresneeds the entity from loot context, which the block-miningtypeprovides asthis. -
Copy the block’s name onto the drop. If your mystic ore is a block entity that can hold a custom name, add a
copy_componentsfunction ("source": "block_entity","include": ["minecraft:custom_name"]) to the diamond entry so a renamed block hands its name to its drop. -
Try It! (a
sequence). Replace one pool’s single entry with asequencecomposite entry whose children each have atable_bonuscondition at increasing levels. Watch howsequencedrops a run (every child from the top until one fails) versusalternatives, which keeps only the first that passes.
What Can Go Wrong
The wrong context type, or a missing parameter. If you set "type": "minecraft:chest" on this
table, the chest context provides no Tool, so match_tool, table_bonus, and apply_bonus
have nothing to read. Conditions that require a missing parameter “always fail if not
provided,” and the load-time check warns you in the output log. Fix: use "type": "minecraft:block"
for a block’s loot, so the Tool and Block state parameters exist.
Confusing table_bonus with apply_bonus. They’re easy to swap because both involve an
enchantment. Remember: table_bonus is a condition (it goes in a conditions list and decides
whether an entry/pool runs, by chance); apply_bonus is a function (it goes in a functions
list and changes the count). Putting one where the other belongs makes the file fail to load.
Expecting copy_nbt to exist. Old tutorials reference a function named copy_nbt. In current
Java Edition the function is copy_custom_data (it copies NBT into the minecraft:custom_data
component), and for components you usually want copy_components instead. Use the current names or
the loot table won’t parse.
A reference that points at the wrong kind of file. The reference function must name an
item modifier file; the reference condition must name a predicate file. Point a function
at a predicate (or vice-versa) and it won’t resolve. Double-check which list (functions or
conditions) your reference sits in.