Without Enough Items — Registry API

Registry API tutorial

Everything you need to teach Without Enough Items about the custom ways your addon lets players obtain items: crafting recipes, loot sources, trades and crafting stations — with copy-paste examples for every case.

WEI does not create the recipe, drop or trade itself. It only keeps a browsable index that players open in-game to see how an item can be obtained. For add-on items, WEI Customizer will suffice in many cases. However, for more complex scenarios (such as a 9x9 crafting table or loot spawned via command), you will need to manually add compatibility using the Registry API.

Registration happens through a Script Event:

system.sendScriptEvent("wei:api_register", JSON.stringify(payload));

1. How the API works

Every registration is a single JSON object with a type field. WEI reads type and decides which of its four entry types to store it as. You can also send a batch of registrations in a single call — see the Multiple registrations section.

TypeWhat it registersRequired fields
craftA way to craft an item at a station.result, recipe_type, recipe_tag, icon
lootItems obtainable from a custom drop source (block or entity).results
tradeItems obtainable from a custom trading table.results, gives
station_tagLinks a crafting-station tag to one or more blocks.tag, blocks

Recommended: call the API from script

Prefer system.sendScriptEvent(id, message) from your own behavior-pack script over the /scriptevent chat command. Calling the API from script lets you build the payload as a real JavaScript object, generate it dynamically (loops, data-driven addons, etc.) and let JSON.stringify() handle the escaping for you — none of which you get typing a command by hand.

import { system } from "@minecraft/server";

function register(payload) {
  system.sendScriptEvent("wei:api_register", JSON.stringify(payload));
}

register({
  "type": "loot",
  "id": "myaddon:ruins_chest",
  "results": ["minecraft:emerald", "myaddon:ancient_relic"],
  "blocks": ["minecraft:chest"]
});
About /scriptevent: the chat/command-block version of scriptevent accepts a much shorter message than the API listener does. It's fine for quick manual tests, but for anything you ship in your addon, call system.sendScriptEvent() from code instead.
Event id: wei:api_register. The message must be a valid JSON string; anything that fails to parse or fails validation is simply ignored (with a warning in the content log).

2. Identifiers & ingredient formats

Item, block and entity IDs are normalized automatically. Anywhere the API accepts an ingredient, you can also point to an entire tag instead of a single item.

Short ID
"iron_ingot"
becomes minecraft:iron_ingot.
Namespaced ID
"myaddon:ruby"
kept exactly as given.
Tag
{"tag":"minecraft:logs"}
matches any item with that tag.

Ingredient with a display count

Inside a craft entry, an ingredient slot can be an object with item and an optional count, so the tooltip shows how many are needed in that slot.

{ "item": "minecraft:iron_ingot", "count": 4 }

Empty visual slot

Use the literal string $invisible to make that position render as invisible (invisible, not an empty slot).

"ingredients": ["minecraft:stick", "$invisible", "minecraft:stick"]
IDs vs. tags: a plain string like "minecraft:logs" is read as one specific item/block ID, not a tag. To match a whole tag you must use the object form {"tag":"..."}.
Trailing numeric suffix is stripped: any ID ending in :<number> (an old-style data value, e.g. "myaddon:dye:5") has that suffix removed automatically before it's stored, becoming "myaddon:dye". This applies to every ID the API touches, not just vanilla ones — avoid numeric suffixes on your own namespaced IDs unless you intend for them to be dropped.

3. Craft entries

Use type: "craft" to document a custom recipe. Pick a recipe_type that matches how the recipe is actually built in-game: grid, combination, input or pot.

Fields every craft entry needs

FieldMeaning
resultID of the item the recipe produces.
recipe_typegrid · combination · input · pot
recipe_tagName of the crafting station this recipe belongs to (shown to the player, and matched against a station tag).
iconTexture path shown as the recipe's icon, e.g. textures/items/my_item.

3.1 grid — a rectangular crafting grid

For recipes where the shape/position of the ingredients matters (like a crafting table). The grid goes from 1×1 to 9×9 (81 slots max). Describe the ingredients either as a flat list or as a pattern + key pair.

dimensions: [x, y]ingredients OR pattern + keymax 81 slots
Example — flat ingredient list
register({
  "type": "craft",
  "result": "myaddon:ruby_block",
  "recipe_type": "grid",
  "recipe_tag": "myaddon:gem_bench",
  "icon": "textures/items/ruby_block",
  "dimensions": [3, 3],
  "ingredients": [
    "myaddon:ruby", "myaddon:ruby", "myaddon:ruby",
    "myaddon:ruby", "myaddon:ruby", "myaddon:ruby",
    "myaddon:ruby", "myaddon:ruby", "myaddon:ruby"
  ]
});
Example — shaped recipe with pattern + key
register({
  "type": "craft",
  "result": "myaddon:reinforced_stick",
  "recipe_type": "grid",
  "recipe_tag": "minecraft:crafting",
  "icon": "textures/items/reinforced_stick",
  "dimensions": [3, 3],
  "pattern": [
    " I ",
    " S ",
    " I "
  ],
  "key": {
    "I": "minecraft:iron_nugget",
    "S": { "item": "minecraft:stick", "count": 1 }
  }
});

Every row in pattern is padded on the right with empty spaces if it's shorter than dimensions[0], and characters not present in key throw a validation error — double-check your key covers every symbol you used.

3.2 combination — up to 4 loose ingredients

For recipes with up to four ingredients where the exact grid position doesn't matter (mixing stations, blenders, etc.).

ingredients: 1–4 items
register({
  "type": "craft",
  "result": "myaddon:healing_potion",
  "recipe_type": "combination",
  "recipe_tag": "myaddon:alchemy_table",
  "icon": "textures/items/healing_potion",
  "ingredients": [
    "myaddon:crushed_herb",
    "minecraft:glass_bottle",
    { "item": "minecraft:sugar", "count": 2 }
  ]
});

3.3 input — a single-item transformation

For recipes where exactly one item turns into the result (a press, a saw, a compactor).

ingredients: exactly 1 item
register({
  "type": "craft",
  "result": "myaddon:iron_plate",
  "recipe_type": "input",
  "recipe_tag": "myaddon:compactor",
  "icon": "textures/items/iron_plate",
  "ingredients": ["minecraft:iron_ingot"]
});

3.4 pot — a grid plus a required container

Same grid rules as grid (up to 9×9, ingredients or pattern+key), plus a mandatory container item — the vessel the result comes out in (a bowl, a bucket, a jar you define).

container: required
register({
  "type": "craft",
  "result": "myaddon:herbal_stew",
  "recipe_type": "pot",
  "recipe_tag": "myaddon:cooking_pot",
  "icon": "textures/items/herbal_stew",
  "dimensions": [3, 3],
  "pattern": [
    "H H",
    " W ",
    "H H"
  ],
  "key": {
    "H": "minecraft:sweet_berries",
    "W": "minecraft:water_bucket"
  },
  "container": "minecraft:bowl"
});

Which recipe_type do I need?

grid
Position matters and it maps to a rectangular grid.
combination
Up to 4 ingredients, position doesn't matter.
input
Exactly one item becomes the result.
pot
A grid recipe that also needs a container item.

4. Loot entries

Use type: "loot" whenever an item can drop from something — a custom block, a custom entity, or both.

FieldRequired?Meaning
resultsYesList of item IDs this source can drop.
idNoReadable name for this loot source, shown to the player.
blocksNoBlock IDs that drop this loot when broken.
entitiesNoEntity IDs that drop this loot when killed.
Example — a custom chest with several possible drops
register({
  "type": "loot",
  "id": "myaddon:ruins_chest",
  "results": [
    "minecraft:emerald",
    "minecraft:gold_ingot",
    "myaddon:ancient_relic"
  ],
  "blocks": ["myaddon:ruined_chest"]
});
Example — a hostile mob drop
register({
  "type": "loot",
  "id": "myaddon:swamp_lurker_drops",
  "results": ["myaddon:slime_gland", "minecraft:rotten_flesh"],
  "entities": ["myaddon:swamp_lurker"]
});
Only blocks
Omit entities entirely — it's optional.
Only entities
Omit blocks entirely — it's optional.

5. Trade entries

Use type: "trade" for a custom trading table (merchant, altar, exchange block — anything that swaps items). gives is the heart of it: it maps each item in results to what the player must pay for it.

FieldRequired?Meaning
resultsYesList of item IDs this table can give out.
givesYesObject mapping each result item ID to the list of items required for it (see below).
idNoReadable name for this trading table.
entitiesNoEntity IDs that offer this table (e.g. a custom merchant).

Shape of a want entry

Each entry inside a gives[result] array is an object { "item": "...", "quantity": N }. quantity can be a fixed integer, or a range object with min/max.

{ "item": "minecraft:emerald", "quantity": 3 }
{ "item": "minecraft:iron_ingot", "quantity": { "min": 2, "max": 5 } }
Example — a simple trade
register({
  "type": "trade",
  "id": "myaddon:forest_merchant",
  "results": ["myaddon:enchanted_bow"],
  "gives": {
    "myaddon:enchanted_bow": [
      { "item": "minecraft:emerald", "quantity": 12 }
    ]
  },
  "entities": ["myaddon:forest_merchant"]
});

An offer that requires two items at once

To describe a single offer that consumes two items together (not two separate alternative offers), wrap the pair of want objects in its own array inside gives[result].

register({
  "type": "trade",
  "id": "myaddon:blacksmith",
  "results": ["myaddon:steel_pickaxe"],
  "gives": {
    "myaddon:steel_pickaxe": [
      [
        { "item": "minecraft:iron_ingot", "quantity": 10 },
        { "item": "minecraft:stick", "quantity": 2 }
      ]
    ]
  },
  "entities": ["myaddon:blacksmith"]
});

Here the pickaxe has one single offer that costs 10 iron ingots and 2 sticks together.

6. Station tags

Use type: "station_tag" to tell WEI which blocks act as a given crafting station. Any craft entry whose recipe_tag matches this tag will list all of these blocks as valid places to craft it.

FieldMeaning
tagThe station name — the same string you use as recipe_tag in your craft entries.
blocksNon-empty list of block IDs that count as this station.
register({
  "type": "station_tag",
  "tag": "myaddon:gem_bench",
  "blocks": ["myaddon:gem_bench", "myaddon:gem_bench_upgraded"]
});
Order doesn't matter: you can register the station_tag before or after the craft entries that use it — WEI just needs both to exist by the time a player opens the item's page.

7. Registering several entries at once

If your addon needs to register many recipes, drops or trades on startup, you don't have to call sendScriptEvent once per entry. Wrap a list of normal registration objects in a top-level multi array and send it as a single event.

register({
  "multi": [
    {
      "type": "craft",
      "result": "myaddon:ruby",
      "recipe_type": "input",
      "recipe_tag": "myaddon:gem_bench",
      "icon": "textures/items/ruby",
      "ingredients": ["myaddon:raw_ruby"]
    },
    {
      "type": "loot",
      "id": "myaddon:ruins_chest",
      "results": ["myaddon:raw_ruby"],
      "blocks": ["myaddon:ruined_chest"]
    },
    {
      "type": "station_tag",
      "tag": "myaddon:gem_bench",
      "blocks": ["myaddon:gem_bench"]
    }
  ]
});

Each entry inside multi follows exactly the same rules described in the sections above — it's simply a convenient way to send a batch of them together instead of firing one Script Event per entry.

8. Limits & debugging

Message size
The stringified JSON sent to wei:api_register must stay under 20,000 characters. Split very large batches across a few calls if needed.
Silent failures
An invalid registration is skipped, not thrown as a game error — check the Content Log for warnings prefixed with [WEI].

Common validation mistakes

SymptomLikely cause
craft entry ignoredMissing recipe_tag or icon — both are required for every recipe_type.
pot craft ignoredMissing container.
combination craft ignoredMore than 4 entries in ingredients.
input craft ignoredingredients doesn't contain exactly 1 entry.
trade entry ignoredgives missing, or not a plain object.
station_tag ignoredblocks is empty or contains a non-string entry.

9. Full scenarios

Two complete, self-contained scenarios combining several entry types for the same content pack.

Scenario A — a new gem workflow

A block drops a raw gem, a station smelts it into a usable gem, and the station itself is registered so both blocks that host it show up.

register({
  "multi": [
    {
      "type": "loot",
      "id": "myaddon:crystal_vein",
      "results": ["myaddon:raw_sapphire"],
      "blocks": ["myaddon:sapphire_ore"]
    },
    {
      "type": "craft",
      "result": "myaddon:sapphire",
      "recipe_type": "input",
      "recipe_tag": "myaddon:gem_furnace",
      "icon": "textures/items/sapphire",
      "ingredients": ["myaddon:raw_sapphire"]
    },
    {
      "type": "station_tag",
      "tag": "myaddon:gem_furnace",
      "blocks": ["myaddon:gem_furnace", "myaddon:gem_furnace_deluxe"]
    }
  ]
});

Scenario B — a trading post

A custom villager-like entity offers two different results, one of them costing a bundled pair of items.

register({
  "type": "trade",
  "id": "myaddon:caravan_trader",
  "results": ["minecraft:saddle", "myaddon:travel_cloak"],
  "gives": {
    "minecraft:saddle": [
      { "item": "minecraft:emerald", "quantity": { "min": 6, "max": 9 } }
    ],
    "myaddon:travel_cloak": [
      [
        { "item": "minecraft:leather", "quantity": 5 },
        { "item": "minecraft:string", "quantity": 3 }
      ]
    ]
  },
  "entities": ["myaddon:caravan_trader"]
});

10. Quick reference

I need to...UseDon't forget
Register a shaped/positional recipecraft + griddimensions
Register up to 4 loose ingredientscraft + combinationmax 4 entries
Register a single-item transformationcraft + inputexactly 1 entry
Register a grid recipe with a containercraft + potcontainer
Register a custom drop sourcelootresults
Register a custom trade tabletraderesults + gives
Register blocks as a stationstation_tagtag + blocks
Send several entries at oncemultiarray of normal registration objects
Before you ship: JSON is valid, type is spelled correctly, every field required for that type is present, IDs/tags follow the formats in section 2, and the total message stays under 20,000 characters.