Appearance
Quickstart: your first mod
You'll learn: the shape of a package, how to add a block, an item and a recipe, how to react to a world event, and how to build, lint and run it. Time: about five minutes.
Status: Built (headless; a plot also runs in the local plots dev stack, see Tooling). Running it in the live world, and inventery-mod dev (your own package hot-reloaded), are Planned.
You need Node 22.18 or newer and a checkout of the plot-runtime branch. Commands run from servers/inventery/, after npm install there.
1. Make a package
A package is a folder with a plot.json and some code. Ours adds lanterns.
lanterns/
plot.json
logic.ts
assets/textures/lantern.png a 16×16 PNGjson
{
"kind": "mod",
"namespace": "lanterns",
"id": "ana.lanterns",
"version": "0.1.0",
"name": "Lanterns",
"apiVersion": 1,
"code": "dist/logic.js",
"assets": "assets"
}kind: "mod": composable content. (The other kind, a runtime, owns a plot's game flow. See Runtimes and mods.)namespace: "lanterns": every id you add becomeslanterns:<name>.id:<owner>.<name>, the package's own id.
2. Add a block, an item and a recipe
ts
// logic.ts
import { scene, ui } from "@inventery/host";
import { blocks, items, recipes, events } from "@inventery/minecraft";
// Hooks the Minecraft layer's events need: re-export the ones you use.
export { onBlockBroken } from "@inventery/minecraft";
// 1. Content. Top-level code runs at plot level when the plot's code starts (and after every restart).
const LANTERN = blocks.add("lantern", { // id "lanterns:lantern"
texture: "textures/lantern.png",
hardness: 0.5,
drops: [{ item: "lanterns:lantern", count: 1 }],
});
items.add("lantern", { texture: "textures/lantern.png", places: "lanterns:lantern" });
recipes.add({
pattern: ["G", "T"],
keys: { G: "inventery:glass", T: "inventery:torch" },
result: { item: "lanterns:lantern", count: 1 },
});That's already a working mod: a content pack. Content is data in registries, so other mods can use your lantern in their own recipes by its id. You don't write any breaking or placing code: the base game's rules run inside the plot, so players place and break lanterns like any block, and a broken one drops its drops.
A pure data pack, with no code at all, is a mod whose
plot.jsonhas"registries": "registries"and nocode: the folder'sblocks.json,items.json,recipes.json(and entities, structures, world-gen features) are added before any code runs (Built).build.mjsandlint.mjscheck every entry. Example:plot/test/fixtures/sdk/lanterns/.
3. React to a world event
Add this below the content:
ts
// 2. Behaviour: after a lantern is broken, flash a light and tell whoever broke it.
events.blockBroken.on(LANTERN, (ev) => {
scene.create({
Transform: { pos: ev.pos },
Light: { color: "gold", intensity: 3, range: 6 },
Lifetime: { seconds: 0.5 }, // the room removes it after half a second
});
if (ev.by) ui.chat(ev.by, "The lantern flickers out.", { color: "gold" });
});World events come in two kinds. Filters run before the base game acts and can refuse it (events.blockBreak.on(...): return false to refuse); listeners hear what it did (events.blockBroken: every cell it changed). Any package's refusal wins.
Two things worth noticing:
- The light goes in the scene (
scene.create). That's how everyone, including people standing outside the plot, sees it. If it should be seen, it's in the scene. (Why.) - The handler runs in the lobby where the block broke. Chat, the scene and storage all act on that lobby only. (Lobbies.)
Prefer no library? The same thing as a plain hook:
ts
import type { BlockChangeEvent } from "@inventery/host";
export function onBlockBroken(ev: BlockChangeEvent) {
if (ev.block !== LANTERN) return; // not ours
// ...the same body...
}4. Lint, build and run
sh
node plot/sdk/lint.mjs path/to/lanterns # the platform's rules, with a fix for each finding
node plot/sdk/build.mjs path/to/lanterns # bundles logic.ts to dist/logic.js, hashes the artifacts
node docs/developers/examples/run.mjs path/to/lanterns --seconds 1 \
--place lanterns:lantern@5,0,4 --break 5,0,4The last line walks a player into the plot in the headless room (with the base game's rules), steps them up to the spot, places a lantern, breaks it, and prints what the player received:
place lanterns:lantern at 5,0,4: true
break 5,0,4: true
lobby draft: suspended
lobby main: 1 player
ana: at 4,0,6, inside in lobby main, plot health 20
inventory: []
CHAT {"scope":"lobby","from":"ana.lanterns","text":"The lantern flickers out.","opts":{"color":"gold"}}(The lantern it dropped lies on the ground a step away; walking over it would pick it up.)
This exact mod lives in examples/lanterns.
5. What you didn't have to do
- No networking. Writing the scene replicates it to every viewer.
- No block rules. Placing, breaking, drops and pickup are the base game's, inside your plot too.
- No lobby bookkeeping. A plot with no runtime of its own runs the default Inventery runtime: one lobby (
"main"), shared with the world. A runtime that sets a lobby size gets lobbies the platform makes and fills. - No save code for the block. Block edits are kept by the host, per lobby.
Next
- Read the mental model (five minutes, and it pays for itself).
- Build a real game: Bedwars.
- See the whole API: Reference.
Coming soon: inventery-mod dev, your own package hot-reloaded in a local room (Tooling). Today: the harness (plot/harness/harness.mjs) for scripted players and renders, and the plots dev stack for the samples in a browser.