Attaching a Trade Table to an Entity
Trade tables live in the BP/trading/ directory of your Behavior Pack. You can organize them into subfolders, such as BP/trading/economy_trades/ or BP/trading/custom/, to keep your project tidy. A trade table does nothing on its own; it must be referenced by an entity behavior file using a trading component.
For modern villagers that use the Village and Pillage leveling UI, you attach the table using the minecraft:economy_trade_table component. For simpler merchants or legacy entities that do not need XP bars and profession badges, you can use the basic minecraft:trade_table component instead. Both components point to the trade table file using a path relative to the Behavior Pack root, starting with trading/.
{
"format_version": "1.20.80",
"minecraft:entity": {
"description": {
"identifier": "my_pack:custom_merchant",
"is_spawnable": true,
"is_summonable": true
},
"components": {
"minecraft:economy_trade_table": {
"display_name": "entity.my_pack:custom_merchant.name",
"table": "trading/custom_merchant_trades.json",
"new_screen": true,
"persist_trades": true,
"convert_trades_economy": false
}
}
}
}Tiers and Trade Groups
A trade table file consists of a root tiers array. Each object inside this array represents a trading level (such as Novice, Apprentice, Journeyman, Expert, and Master). When using minecraft:economy_trade_table, you define the total_exp_required on each tier to dictate how much trader XP the entity needs before that tier unlocks. The first tier always requires 0 XP.
Inside a tier, you can either list a flat trades array—which unlocks every single trade in that list when the tier is reached—or you can wrap your trades inside a groups array. A group contains a pool of potential trades and a num_to_select property. When the entity generates its offers for that tier, it randomly picks num_to_select trades from the group and discards the rest. This is how two villagers of the same profession end up offering different items at the same level.
{
"tiers": [
{
"total_exp_required": 0,
"groups": [
{
"num_to_select": 1,
"trades": [
{
"wants": [
{
"item": "minecraft:emerald",
"quantity": 2
}
],
"gives": [
{
"item": "minecraft:bread",
"quantity": 6
}
],
"trader_exp": 2,
"max_uses": 12,
"reward_exp": true
},
{
"wants": [
{
"item": "minecraft:wheat",
"quantity": {
"min": 18,
"max": 22
}
}
],
"gives": [
{
"item": "minecraft:emerald",
"quantity": 1
}
],
"trader_exp": 2,
"max_uses": 16,
"reward_exp": true
}
]
}
]
},
{
"total_exp_required": 10,
"trades": [
{
"wants": [
{
"item": "minecraft:emerald",
"quantity": 5
},
{
"item": "minecraft:apple",
"quantity": 1
}
],
"gives": [
{
"item": "minecraft:golden_apple",
"quantity": 1
}
],
"trader_exp": 5,
"max_uses": 6,
"reward_exp": true
}
]
}
]
}Defining Individual Trades
Each trade object inside a trades array defines a single transaction row in the merchant UI. The transaction is split into a wants array (what the player must pay) and a gives array (what the player receives). While a trade can ask for one or two input items in the wants array, the gives array only ever dispenses its first item entry in the standard trading UI.
Quantities can be written as a fixed integer or as an object with min and max keys. If you use a range, the game rolls a fixed number within that range at the moment the trade is unlocked by the entity. If you want to specify a block or item variant that still relies on data values, you append a colon and the data integer directly to the item identifier string, such as "minecraft:planks:2", though modern flattened identifiers are strongly preferred.
| Field | What it does | Notes |
|---|---|---|
| wants | Array of 1 or 2 item objects the player must provide. | Adding more than 2 items is ignored by the trading UI. |
| gives | Array containing the 1 item object the player receives. | Only the first entry in this array is awarded. |
| max_uses | Number of times this trade can be completed before locking. | Resets when the entity works at its workstation, if configured to resupply. |
| trader_exp | Amount of XP added to the merchant's level bar per trade. | Only applies when using minecraft:economy_trade_table. |
| reward_exp | Boolean that controls whether the player gets green XP orbs. | Defaults to true if omitted in most vanilla tables. |
| price_multiplier | Float placed inside a wants item entry to control demand scaling. | Determines how sharply the price rises or falls from reputation and curing. |
| choice | Array of item objects used inside a wants or gives entry. | Picks one item randomly from the array when generating the offer. |
Random Choices and Item Functions
If you want a single trade slot to randomly pick between different items without duplicating the entire trade entry, you can replace a standard item definition in wants or gives with a choice array. The game selects one item entry from the choice list at the time the trade is generated.
Like loot tables, item entries inside a gives array support a functions array to modify the output item before handing it to the player. This is essential for selling enchanted gear, enchanted books, or named items. Note that not every loot table function works inside a trade table, and functions placed inside the wants array are generally ignored by the trade matching logic.
{
"tiers": [
{
"total_exp_required": 0,
"trades": [
{
"wants": [
{
"item": "minecraft:emerald",
"quantity": {
"min": 7,
"max": 12
},
"price_multiplier": 0.2
}
],
"gives": [
{
"choice": [
{
"item": "minecraft:iron_sword",
"quantity": 1,
"functions": [
{
"function": "enchant_with_levels",
"treasure": false,
"levels": {
"min": 5,
"max": 19
}
}
]
},
{
"item": "minecraft:iron_axe",
"quantity": 1,
"functions": [
{
"function": "enchant_with_levels",
"treasure": false,
"levels": {
"min": 5,
"max": 19
}
}
]
}
]
}
],
"trader_exp": 3,
"max_uses": 3,
"reward_exp": true
}
]
}
]
}Trade Persistence and Restocking
Once a player opens the trading screen of an entity with persist_trades set to true, the generated trade list is saved directly into that specific entity's NBT data in the world. Editing your JSON file later will not update merchants that have already generated their trades; you must spawn a fresh entity to test changes to your trade table.
When a player exhausts a trade by hitting its max_uses limit, a red X appears over the arrow in the UI. For an economy trader to unlock locked trades, the entity needs AI goals and scheduling components that allow it to pathfind to and interact with a workstation block, just as vanilla villagers do. If you are making a stationary NPC shopkeeper that should never run out of stock, set max_uses to a very high integer such as 999999.
Which versions this applies to
Applies to Bedrock Edition 1.11.0 and newer (including 1.20.x and 1.21.x). No experimental toggles are required to use trade tables or trading components.
When it does not work
| What you see | Why | The fix |
|---|---|---|
| The trading UI opens with no trades listed, or right-clicking the entity does nothing at all. | The path in the entity's table property does not match the actual file location, or the trade table has a JSON syntax error. | Ensure the path in your entity file starts with "trading/" (never include "BP/") and check the Content Log for JSON parse errors in your trade table. |
| A trade shows an empty slot or an 'Unknown' dirt block icon in the merchant window. | An item identifier inside wants or gives is misspelled, uses a Java Edition name, or refers to a custom item whose pack is missing. | Verify every item string against valid Bedrock identifiers (for example, use flattened names like "minecraft:oak_planks" on modern versions) and confirm your custom item identifiers include their namespace. |
| Changes made to the trade table JSON do not show up in-game when testing. | You are testing on an already-spawned entity that has cached its old trades via persist_trades. | Summon a brand new instance of the entity after reloading the world, or set persist_trades to false temporarily while debugging. |
| Higher tiers never unlock even after trading with the entity dozens of times. | The trades in the lower tiers are missing the trader_exp field, or the entity is using minecraft:trade_table instead of minecraft:economy_trade_table. | Switch the entity component to minecraft:economy_trade_table, ensure every tier has total_exp_required defined in ascending order, and give each trade a positive trader_exp value. |
Reference, straight from the game files
Read out of the game itself, so the ids match the version you are building for.
