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.
| Type | What it registers | Required fields |
|---|---|---|
craft | A way to craft an item at a station. | result, recipe_type, recipe_tag, icon |
loot | Items obtainable from a custom drop source (block or entity). | results |
trade | Items obtainable from a custom trading table. | results, gives |
station_tag | Links 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"]
});
system.sendScriptEvent() from code instead.
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.
"iron_ingot"becomes
minecraft:iron_ingot."myaddon:ruby"kept exactly as given.
{"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"]
{"tag":"..."}.
:<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
| Field | Meaning |
|---|---|
result | ID of the item the recipe produces. |
recipe_type | grid · combination · input · pot |
recipe_tag | Name of the crafting station this recipe belongs to (shown to the player, and matched against a station tag). |
icon | Texture 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.
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.).
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).
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).
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?
Position matters and it maps to a rectangular grid.
Up to 4 ingredients, position doesn't matter.
Exactly one item becomes the result.
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.
| Field | Required? | Meaning |
|---|---|---|
results | Yes | List of item IDs this source can drop. |
id | No | Readable name for this loot source, shown to the player. |
blocks | No | Block IDs that drop this loot when broken. |
entities | No | Entity IDs that drop this loot when killed. |
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"]
});
Omit entities entirely — it's optional.
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.
| Field | Required? | Meaning |
|---|---|---|
results | Yes | List of item IDs this table can give out. |
gives | Yes | Object mapping each result item ID to the list of items required for it (see below). |
id | No | Readable name for this trading table. |
entities | No | Entity 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.
| Field | Meaning |
|---|---|
tag | The station name — the same string you use as recipe_tag in your craft entries. |
blocks | Non-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"]
});
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
The stringified JSON sent to
wei:api_register must stay under 20,000 characters. Split very large batches across a few calls if needed.An invalid registration is skipped, not thrown as a game error — check the Content Log for warnings prefixed with
[WEI].Common validation mistakes
| Symptom | Likely cause |
|---|---|
| craft entry ignored | Missing recipe_tag or icon — both are required for every recipe_type. |
| pot craft ignored | Missing container. |
| combination craft ignored | More than 4 entries in ingredients. |
| input craft ignored | ingredients doesn't contain exactly 1 entry. |
| trade entry ignored | gives missing, or not a plain object. |
| station_tag ignored | blocks 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... | Use | Don't forget |
|---|---|---|
| Register a shaped/positional recipe | craft + grid | dimensions |
| Register up to 4 loose ingredients | craft + combination | max 4 entries |
| Register a single-item transformation | craft + input | exactly 1 entry |
| Register a grid recipe with a container | craft + pot | container |
| Register a custom drop source | loot | results |
| Register a custom trade table | trade | results + gives |
| Register blocks as a station | station_tag | tag + blocks |
| Send several entries at once | multi | array of normal registration objects |
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.