# Villager Trades

> Define custom villager trades and trade sets for Minecraft 26.1+ with Kore's type-safe Kotlin DSL. Configure items, prices, demand, and profession-level trade tables without hand-writing JSON.

Updated 2026-10-26. Canonical: https://kore.ayfri.com/docs/data-driven/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
```

For the full JSON specification see:

- [Minecraft Wiki: Villager trade definition](https://minecraft.wiki/w/Villager_trade_definition)
- [Minecraft Wiki: Trade set](https://minecraft.wiki/w/Trade_set)

---

## 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.

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

### 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`:

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

Two-input trades use `additionalWants` for the second slot:

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

### 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](https://kore.ayfri.com/docs/data-driven/loot-tables.md#context-providers), including the ID of a
[context provider file](https://kore.ayfri.com/docs/data-driven/loot-tables.md#context-provider-files) through `reference(...)`:

```kotlin
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)
}
```

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

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

### 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](https://kore.ayfri.com/docs/data-driven/loot-tables.md#context-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](https://kore.ayfri.com/docs/data-driven/loot-tables.md#context-provider-files) through `reference(...)`:

```kotlin
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)
}
```

### 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:

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

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

```kotlin
givenItemModifier {
    reference(ItemModifierArgument("enchant_boots", "mypack"))
}
```

See [Item Modifiers](https://kore.ayfri.com/docs/data-driven/item-modifiers.md) 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.

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

See [Predicates](https://kore.ayfri.com/docs/data-driven/predicates.md) 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.

```kotlin
doubleTradePriceEnchantments = listOf(Enchantments.BINDING_CURSE)
```

---

## 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.

```kotlin
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)
    }
}
```

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:

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

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:

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

### 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

```kotlin
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
    }
}
```

---

## See Also

- [Item Modifiers](https://kore.ayfri.com/docs/data-driven/item-modifiers.md) - Apply functions to the item a villager gives
- [Predicates](https://kore.ayfri.com/docs/data-driven/predicates.md) - Gate trade availability via `merchantPredicate`
- [Enchantments](https://kore.ayfri.com/docs/data-driven/enchantments.md) - `doubleTradePriceEnchantments` and enchanting trade outputs
- [Tags](https://kore.ayfri.com/docs/data-driven/tags.md) - Group trades into reusable `villager_trade` tags for trade sets
- [Loot Tables](https://kore.ayfri.com/docs/data-driven/loot-tables.md) - Related data-driven item generation system

### External Resources

- [Minecraft Wiki: Villager trade definition](https://minecraft.wiki/w/Villager_trade_definition)
- [Minecraft Wiki: Trade set](https://minecraft.wiki/w/Trade_set)
- [Minecraft Wiki: Trading](https://minecraft.wiki/w/Trading) - How the vanilla trading system works
- [Minecraft Wiki: Villager](https://minecraft.wiki/w/Villager) - Villager behavior, professions, and leveling
