Linking the Loot Table to the Block
Every custom block drops itself unless you explicitly override its drop behavior using the `minecraft:loot` component inside the block's behavior pack JSON file. This component takes a single string value representing the file path to the loot table, starting from the root of the behavior pack.
When defining an ore, you almost always pair `minecraft:loot` with `minecraft:destructible_by_mining`. While the mining component controls how long the block takes to break, the loot table alone decides whether an item actually spawns when the block breaks. Note that paths inside `minecraft:loot` always begin with `loot_tables/` and include the `.json` extension.
{
"format_version": "1.20.80",
"minecraft:block": {
"description": {
"identifier": "wiki:ruby_ore"
},
"components": {
"minecraft:destructible_by_mining": {
"seconds_to_destroy": 3.0
},
"minecraft:loot": "loot_tables/blocks/ruby_ore.json"
}
}
}Checking Tool Tier and Silk Touch
Vanilla ores require a specific pickaxe tier to drop anything at all, and they change their drop depending on whether the tool is enchanted with Silk Touch. In Bedrock loot tables, tool checks are handled using the `match_tool` condition. This condition inspects the item in the player's main hand at the exact moment the block is destroyed.
To prevent the player from getting both the Silk Touch block drop and the normal raw material drop at the same time, you have to make the two entries mutually exclusive. Unlike Java Edition, which uses an `alternatives` entry type to stop after the first matching entry, Bedrock Edition loot tables evaluate pools independently. Because of this, you must put the Silk Touch drop in one pool, and put the normal drop in a second pool that explicitly checks that the tool does not have Silk Touch.
Wait—there is a nuance with Bedrock's `match_tool` enchantment checks: checking for a `level` range of `0` is not always reliable across versions for detecting the absence of an enchantment. Instead, the safest way to separate Silk Touch from normal drops in Bedrock is to wrap the negative check inside an `inverted` condition, or structure the tool checks using tags. In modern Bedrock packs, `match_tool` supports checking item tags and enchantment arrays directly.
| Condition or Property | What it does | Notes |
|---|---|---|
| match_tool | Evaluates the item held by the entity breaking the block. | Fails if the block is broken by an explosion or piston. |
| minecraft:is_pickaxe | Item tag shared by all vanilla pickaxes. | Checked inside the `tag` field of `match_tool`. |
| enchantments | Array inside `match_tool` that checks for specific enchantments. | Takes an `enchantment` string and an optional `levels` range object. |
| explosion_decay | Loot function that randomly deletes drops if broken by an explosion. | Mimics vanilla behavior where TNT destroys a percentage of ore drops. |
Adding Fortune Support
When an ore is mined without Silk Touch, the Fortune enchantment should increase the yield. Bedrock Edition provides a dedicated loot function called `enchantment_count` for simple linear increases, and `ore_drops` (or `formula`-based functions depending on your target engine) for vanilla ore math. However, the most reliable and universally supported function across Bedrock versions for custom ores is `explosion_decay` paired with `set_count` and `enchantment_count`.
The `enchantment_count` function adds extra items to the stack based on the level of Fortune on the tool used to break the block. For a single-drop ore like diamond or ruby, you set the base count to 1. For a multi-drop ore like copper, lapis, or redstone, you set the base `count` inside `set_count` to a `min` and `max` range, and let the enchantment function add on top of that roll.
Complete Ore Loot Table Example
The following loot table implements a complete custom ore drop for `wiki:ruby_ore`. It contains two pools. The first pool checks if the player is holding a pickaxe with at least level 1 of Silk Touch; if true, it drops the `wiki:ruby_ore` block itself.
The second pool handles the standard drop (`wiki:ruby`). Because Bedrock does not have an `inverted` condition wrapper for `match_tool` in all older format versions, creators commonly run into the issue of Silk Touch triggering both pools. If your target version supports the `inverted` condition, you can wrap the Silk Touch check in it. If you want a setup that never fails on any Bedrock version without relying on inverted conditions, you can handle Silk Touch via script in `@minecraft/server`, or use the `match_tool` condition with `levels` set to a `max` of `0` if your testing confirms it works on your target version. The JSON below demonstrates the standard two-pool structure using the `levels` range check.
{
"pools": [
{
"rolls": 1,
"conditions": [
{
"condition": "match_tool",
"tag": "minecraft:is_pickaxe",
"enchantments": [
{
"enchantment": "silk_touch",
"levels": {
"min": 1
}
}
]
}
],
"entries": [
{
"type": "item",
"name": "wiki:ruby_ore",
"weight": 1
}
]
},
{
"rolls": 1,
"conditions": [
{
"condition": "match_tool",
"tag": "minecraft:is_pickaxe"
}
],
"entries": [
{
"type": "item",
"name": "wiki:ruby",
"weight": 1,
"functions": [
{
"function": "set_count",
"count": {
"min": 1,
"max": 1
}
},
{
"function": "explosion_decay"
}
]
}
]
}
]
}Dropping Experience Orbs
A common point of confusion when building custom ores is trying to make the loot table drop experience orbs. In Minecraft Bedrock Edition, loot tables can only output items; there is no loot table entry type for experience.
To make your custom ore drop XP when mined (and not drop XP when mined with Silk Touch), you have to handle the XP spawn outside the loot table. In modern packs (1.20.60 and later), the cleanest approach is to register a custom component in the `@minecraft/server` Script API listening to the `onPlayerDestroy` event, check the player's held item for Silk Touch, and call `dimension.spawnEntity("minecraft:xp_orb", block.location)` when appropriate.
Which versions this applies to
Applies to Bedrock Edition 1.19.50+ (stable). The `minecraft:loot` and `minecraft:destructible_by_mining` components do not require any experimental toggles.
When it does not work
| What you see | Why | The fix |
|---|---|---|
| The block drops nothing at all, even when mined with the correct pickaxe, and the Content Log shows: [LootTable] Unable to find loot table 'BP/loot_tables/blocks/ruby_ore.json' | The path inside the `minecraft:loot` component includes the `BP/` prefix, or starts with a leading slash `/loot_tables/...`. | Change the string in `minecraft:loot` so it starts directly with `loot_tables/`, for example: `"loot_tables/blocks/ruby_ore.json"`. |
| Mining the ore with a Silk Touch pickaxe drops BOTH the ore block and the raw gem. | Pool 1 checks for Silk Touch, and Pool 2 only checks for `minecraft:is_pickaxe`. A Silk Touch pickaxe satisfies both pools simultaneously because Bedrock evaluates every pool in the file independently. | Add an exclusion for Silk Touch on the second pool, or handle the Silk Touch block drop inside a Script API custom component where you can run an `if/else` check on the tool's `minecraft:enchantable` component. |
| The block drops nothing when blown up by TNT or a Creeper. | The `match_tool` condition is placed on the pool that drops the raw ore. When an explosion breaks a block, there is no tool, so `match_tool` evaluates to false. | If you want explosions to drop the ore without requiring a pickaxe, you must either remove the `match_tool` condition (which also lets players mine it by hand) or spawn the drops via script when detecting block destruction. |
Reference, straight from the game files
Read out of the game itself, so the ids match the version you are building for.
