Villager Trades

Villager trades are data-driven JSON files introduced in Minecraft Java Edition 26.1 that define what individual trades a villager can offer and how they are grouped into trade sets per profession level. This system fully replaces the old hardcoded trade lists and lets data packs control everything about villager economies - what items are bought and sold, at what quantities, for how many uses, with what item modifier functions applied to the output, and under what conditions.

Overview

The trade system uses two file types that work together:

  • villager_trade - a single trade offer: inputs the villager wants, the output it gives, limits, and rewards.
  • trade_set - a pool of villager_trade references that Minecraft samples from when a villager gains a level.

A villager profession's trade list is built from a stack of trade sets, one per level. Each time a villager levels up, Minecraft draws amount trades at random from the appropriate trade set.

File Structure

data/<namespace>/villager_trade/<name>.json
data/<namespace>/trade_set/<name>.json
Null

For the full JSON specification see:


Villager Trade

Creating a Trade

Use villagerTrade on your DataPack. Call wants and gives inside the block to set the required items - both accept an ItemArgument, an optional count, and an optional component builder.

datapack.villagerTrade("wheat_for_emerald") {
    wants(Items.WHEAT, count = 20)
    gives(Items.EMERALD)
    maxUses(12)
    xp(1)
}
Kotlin

Item Slots

wants, gives, and additionalWants all follow the same shape: an item id, an optional stack size, and an optional block to set data components on the item. gives is an item stack with a fixed count:

datapack.villagerTrade("enchanted_sword") {
    wants(Items.EMERALD, count = 30)
    gives(Items.DIAMOND_SWORD) {
        enchantments {
            add(Enchantments.SHARPNESS, 5)
        }
    }
    maxUses(3)
    xp(30)
}
Kotlin

Two-input trades use additionalWants for the second slot:

datapack.villagerTrade("book_trade") {
    wants(Items.EMERALD, count = 5)
    additionalWants(Items.BOOK)
    gives(Items.ENCHANTED_BOOK)
    maxUses(1)
    xp(15)
}
Kotlin

Trade Costs

wants and additionalWants are trade costs (TradeCost), written as { "id": ..., "count": ..., "components": ... }:

Field Type Description
components Components? Components the traded item must match exactly, any item of that type matches when unset.
count ContextIntProvider? Number of items requested, 1 when unset. Rolled each time the trade is offered.
id ItemArgument Item requested.

count takes a fixed number or an int provider, including the ID of a context provider file through reference(...):

datapack.villagerTrade("wheat_for_emerald") {
    wants(Items.WHEAT, count = 20)  // "wants": {"count": 20, "id": "minecraft:wheat"}
    gives(Items.EMERALD)
}

datapack.villagerTrade("variable_price") {
    wants(Items.WHEAT, 16..24)  // "count": {"type": "minecraft:uniform", "max": 24, "min": 16}
    additionalWants(Items.PAPER, reference(contextIntProvider("paper_cost", 2)))  // "count": "my_pack:paper_cost"
    gives(Items.EMERALD)
}
Kotlin

The component block restricts the cost to items carrying exactly those component values, here an undamaged sword:

datapack.villagerTrade("sword_for_emeralds") {
    wants(Items.DIAMOND_SWORD) {
        damage(0)  // "components": {"damage": 0}
    }
    gives(Items.EMERALD, count = 8)
}
Kotlin

In game: the three trades above in a merchant's offer list, click one to select it

VillagerTrade Fields

All fields are optional except wants and gives, which must be set before the data pack is generated.

Field Type Description
additionalWants TradeCost? Second item the villager requests alongside wants.
doubleTradePriceEnchantments InlinableList<EnchantmentOrTagArgument>? Enchantments on the player's item that double the trade cost.
givenItemModifier InlinableItemModifier? Item modifier applied to the output item before delivery.
gives ItemStack? Item the villager offers in return. Required.
maxUses ContextIntProvider? Maximum uses before the trade locks. Unlocks when the villager restocks.
merchantPredicate PredicateCondition? Condition checked against the merchant entity; trade only appears when it passes.
reputationDiscount ContextFloatProvider? Price multiplier applied based on the player's village reputation.
wants TradeCost Primary item the villager requests. Required.
xp ContextIntProvider? XP points awarded to the villager when the trade completes.

maxUses and xp take int providers (maxUses(12), xp(uniform(1, 5))), or a range drawn uniformly (maxUses(8..12), xp(1..5)), reputationDiscount a float provider (reputationDiscount(0.05f)) or a float range (reputationDiscount(0.05f..0.1f)). Each one also takes the ID of a context provider file through reference(...):

val uses = contextIntProvider("trade_uses", 8)

villagerTrade("wheat_for_emerald") {
    wants(Items.WHEAT, count = 20)
    gives(Items.EMERALD)
    maxUses = reference(uses)  // "max_uses": "my_pack:trade_uses"
    reputationDiscount(0.05f)
}
Kotlin

Applying Item Modifiers to the Output

Use givenItemModifier to run an item modifier on the item the villager gives. This is how you add random enchantments, custom lore, or any other post-processing:

datapack.villagerTrade("random_enchanted_book") {
    wants(Items.EMERALD, count = 10)
    additionalWants(Items.BOOK)
    gives(Items.ENCHANTED_BOOK)
    givenItemModifier {
        enchantRandomly()
    }
    maxUses(1)
    xp(15)
}
Kotlin

One function is written as an object, several functions as a list. An item modifier file is applied by its ID with reference:

givenItemModifier {
    reference(ItemModifierArgument("enchant_boots", "mypack"))
}
Kotlin

See Item Modifiers for all available functions.

Gating a Trade with a Predicate

merchantPredicate is a condition evaluated against the villager entity. The trade only appears in the merchant's offer list when the condition passes - useful for biome-locked or NBT-gated trades.

datapack.villagerTrade("desert_trade") {
    wants(Items.SAND, count = 8)
    gives(Items.GLASS)
    merchantPredicate = LocationCheck(predicate = LocationPredicate(biomes = listOf(Biomes.DESERT)))
    maxUses(16)
    xp(2)
}
Kotlin

See Predicates for all available condition types.

Double-Price Enchantments

doubleTradePriceEnchantments lists enchantments that, when present on the player's traded-in item, cause the villager to charge twice as much. Vanilla uses this for the Curse of Binding and similar effects.

doubleTradePriceEnchantments = listOf(Enchantments.BINDING_CURSE)
Kotlin

Trade Set

Creating a Trade Set

A TradeSet groups references to villager_trade files and controls how many are rolled per level. Everything is set inside the block: trades takes the references returned by villagerTrade (or trade tags), and amount takes the number of trades rolled out of the pool.

datapack {
    val wheatTrade = villagerTrade("wheat_for_emerald") {
        wants(Items.WHEAT, count = 20)
        gives(Items.EMERALD)
        xp(1)
    }

    val potatoTrade = villagerTrade("potato_for_emerald") {
        wants(Items.POTATO, count = 26)
        gives(Items.EMERALD)
        xp(1)
    }

    tradeSet("novice_farmer") {
        trades(wheatTrade, potatoTrade)
        amount(2)
    }
}
Kotlin

A file name holding slashes lands in subfolders, the way vanilla groups its sets per profession and level, so tradeSet("farmer/level_1") writes data/<namespace>/trade_set/farmer/level_1.json.

Sampling with Tags

Individual trade references and tag references mix in the same pool, and trade appends a single one to what is already there:

tradeSet("apprentice_farmer") {
    trades(Tags.VillagerTrade.Farmer.LEVEL_2, myCustomTrade)
    trade(VillagerTradeTagArgument("farmer_apprentice", "mymod"))
    amount(1, 3)
    allowDuplicates = false
}
Kotlin

A pool holding a single entry serializes as a plain string rather than an array, matching vanilla.

Reproducible Rolls

randomSequence seeds the rolls from a named random sequence, so the same set always produces the same trades for a given world seed:

tradeSet("farmer/level_1") {
    trades(Tags.VillagerTrade.Farmer.LEVEL_1)
    amount(2)
    randomSequence("trade_set/farmer/level_1", "mypack")
}
Kotlin

TradeSet Fields

Field Type Description
allowDuplicates Boolean? Whether the same trade can be rolled more than once. Defaults to false.
amount ContextIntProvider How many trades are rolled out of the set. Defaults to 1.
randomSequence RandomSequenceArgument? Named random sequence the rolls are seeded from, non-deterministic when unset.
trades InlinableList<VillagerTradeOrTagArgument> Trade references or tags rolled from.

TradeSet Builders

Builder Description
amount(value) Rolls exactly value trades.
amount(min, max) Rolls between min and max trades, both included, like amount(min..max).
amount(range) The same from a range, amount(1..3). Equal ends roll exactly that many, and an inverted range such as 3..1 throws.
amount(provider) Any int provider (binomial(4, 0.5f)), or a reference(id) to a provider file.
randomSequence(name, ns) Seeds the rolls from a named random sequence.
trade(trade) Appends one trade or tag to the pool.
trades(vararg / list) Sets the pool, replacing the entries already there.

Full Example: Custom Farmer Profession Level 1

datapack {
    val hayTrade = villagerTrade("hay_for_emerald") {
        wants(Items.HAY_BLOCK, count = 1)
        gives(Items.EMERALD)
        maxUses(16)
        xp(2)
    }

    val wheatTrade = villagerTrade("wheat_for_emerald") {
        wants(Items.WHEAT, count = 20)
        gives(Items.EMERALD)
        maxUses(16)
        xp(1)
    }

    val breadTrade = villagerTrade("emerald_for_bread") {
        wants(Items.EMERALD)
        gives(Items.BREAD, count = 6)
        maxUses(16)
        xp(1)
    }

    tradeSet("custom_farmer_novice") {
        trades(hayTrade, wheatTrade, breadTrade)
        amount(2)
        allowDuplicates = false
    }
}
Kotlin

See Also

  • Item Modifiers - Apply functions to the item a villager gives
  • Predicates - Gate trade availability via merchantPredicate
  • Enchantments - doubleTradePriceEnchantments and enchanting trade outputs
  • Tags - Group trades into reusable villager_trade tags for trade sets
  • Loot Tables - Related data-driven item generation system

External Resources