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 fixed count, and an optional component builder.

datapack.villagerTrade("wheat_for_emerald") {
    wants(Items.WHEAT, count = 20)
    gives(Items.EMERALD)
    maxUses = constant(12f)
    xp = constant(1f)
}
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.

datapack.villagerTrade("enchanted_sword") {
    wants(Items.EMERALD, count = 30)
    gives(Items.DIAMOND_SWORD) {
        enchantments {
            add(Enchantments.SHARPNESS, 5)
        }
    }
    maxUses = constant(3f)
    xp = constant(30f)
}
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 = constant(1f)
    xp = constant(15f)
}
Kotlin

VillagerTrade Fields

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

Field Type Description
additionalWants ItemStack? Second item slot the villager requests alongside wants.
doubleTradePriceEnchantments InlinableList<EnchantmentOrTagArgument>? Enchantments on the player's item that double the trade cost.
givenItemModifiers ItemModifierAsList? Item modifier functions applied to the output item before delivery.
gives ItemStack? Item the villager offers in return. Required.
maxUses NumberProvider? 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 NumberProvider? Price multiplier applied based on the player's village reputation.
wants ItemStack? Primary item the villager requests. Required.
xp NumberProvider? XP points awarded to the villager when the trade completes.

Applying Item Modifiers to the Output

Use givenItemModifiers to run item modifier functions 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)
    givenItemModifiers {
        enchantRandomly()
    }
    maxUses = constant(1f)
    xp = constant(15f)
}
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 = constant(16f)
    xp = constant(2f)
}
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 offered per level. Pass the list of trade references (returned by villagerTrade) and an amount provider:

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

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

datapack.tradeSet(
    "novice_farmer",
    trades = listOf(wheatTrade, potatoTrade),
    amount = constant(2f),
)
Kotlin

Sampling with Tags

You can mix individual trade references with tag references in the same pool:

datapack.tradeSet(
    "apprentice_farmer",
    trades = listOf(
        VillagerTradeTagArgument("farmer_apprentice", "mymod"),
        myCustomTrade,
    ),
    amount = uniform(constant(1f), constant(3f)),
) {
    allowDuplicates = false
}
Kotlin

TradeSet Fields

Field Type Description
allowDuplicates Boolean? Whether the same trade can be drawn more than once per level-up.
amount NumberProvider How many trades are drawn from this set when a villager levels up.
randomSequence RandomSequenceArgument? Named random sequence for reproducible sampling.
trades InlinableList<VillagerTradeOrTagArgument> Trade references or tags to sample from.

Full Example: Custom Farmer Profession Level 1

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

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

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

    tradeSet(
        "custom_farmer_novice",
        trades = listOf(hayTrade, wheatTrade, breadTrade),
        amount = constant(2f),
    ) {
        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