Reference
Manifest
On this page
A plugin's manifest tells the server who the plugin is and what it wants. The server reads it
once, at load, so it can decide whether to call a plugin without calling it. A Rust plugin
returns a Manifest from Plugin::manifest. A Wasm guest emits it as JSON from
rustmc_manifest (the ABI).
Every field but name may be left out. Unknown JSON fields are ignored.
| Field | Rust | Wasm JSON | Default | Meaning |
|---|---|---|---|---|
| name | Manifest::new(name, ..) | "name" | required | unique among loaded plugins; letters, digits, _ and -, 1 to 64 characters. It is the key for the plugin's stored data, its folder plugins/<name>/, its log tag, and the from other plugins see on the bus |
| version | Manifest::new(.., version) | "version" | "0" | shown by /plugins |
| events | .hearing(&[..]) | "events": [..] | all events | event names this plugin hears |
| priority | .at(Priority::..) | "priority" | normal | first, early, normal, late or observer: the order handlers run in, for events and hooks |
| commands | .with_command(Command::new(name, description, level)), .governed_by(node) | "commands": [{"name", "description", "level", "node"}] | none | commands this plugin answers to, without the slash (below) |
| hooks | .hooking(point, Mode::..) | "hooks": [{"point", "mode"}] | none | hook points and the mode for each: prepend, override or append |
| listens | .listening(&[topic]) | "listens": [topic] | none | plugin-bus topics heard: exact, family.* or * (the bus) |
| settings | .with_setting(Setting::new(..)) | "settings": [{..}] | none | settings the server keeps and the web portal edits (below) |
| pages | .with_page(Page::new(id, title)) | "pages": [{"id", "title"}] | none | pages this plugin adds to the web portal (below) |
| disables | .disabling(&[system]) | "disables": [system] | none | vanilla systems switched off while the plugin is loaded (below) |
| fail_closed | .failing_closed(&[event]) | "fail_closed": [event] | none | cancellable events whose handler, if it fails or is paused, cancels rather than passes |
| extended | .extending(&[key]) | "extended": [key] | none | handlers given the extended fuel budget (limits) |
events and hooks default in opposite directions on purpose. Being told about something
costs a plugin nothing it did not ask for. Being run instead of the game is something a
plugin has to ask for.
No manifest field grants access to host calls: every plugin may make every call. What reaches
outside the server (outbound HTTP to a fixed URL, listening on a port) needs the server owner's
grant in rustmc.yml instead (host calls).
Load-time checks
A manifest is refused, and the plugin not loaded, when:
- the name is empty, longer than 64 characters, or holds anything but letters, digits,
_and-; - a plugin of the same name is already loaded;
- a setting's key is not
[a-z0-9_]{1,64}, is declared twice, is anenumwith no values, or has a default its own type refuses; - a page id is not
[a-z0-9_-]{1,32}; - (Wasm) a hook names no
point, or a mode other thanprepend,overrideorappend; - (Wasm) a command names no
name, or a setting has an unknowntype; - (Wasm)
hooksis not empty and the module does not exportrustmc_on_hook, orlistensis not empty and it does not exportrustmc_on_message.
A plugin that names extended handlers is logged once at load, so the owner can see it.
Subscription rules
- Events. Exact names from the event list. No families, no wildcards. An empty or
missing list hears every event. A name that is no event matches nothing, so
["none"]hears nothing. - Hooks. A point is an exact name (
block.hardness) or a family ending in.*(packet.play.*,command.*), or*for every hook point. One plugin may hook the same point in several modes. - Topics (
listens) take exact names,family.*, or*. - Handler keys (
extended) are an event name,hook:<point>(a family ashookstakes it),menu(clicks in this plugin's menus),call(other plugins' calls) ortopic:<topic>. fail_closednames events. Only a cancellable event can fail closed; any other name is ignored. A Wasm guest that was skipped because it was busy on another thread also counts as failed here.
Commands
| Field | Meaning |
|---|---|
name | without the slash |
description | one line, for /help |
level | the operator level needed, 0 to 4, on the game's scale. Default 0 |
node | the permission node that governs it, passed to a permissions plugin's permission.commands hook. Optional |
A command is sent to clients in the command tree, so it appears in tab completion. When a player
runs it, the plugin hears command_run with command (the name) and rest (the line after it,
unparsed). Completions while typing are asked of the plugin through rustmc_on_message
(the ABI). Each command is also a hook point,
command.<name> (commands).
Settings
Each entry of settings is {"key", "type", "default", "title"?, "help"?, "group"?, "min"?, "max"?, "max_length"?, "max_items"?, "values"?, "secret"?}.
type | Value | Limits |
|---|---|---|
boolean | true / false | |
integer | whole number | min, max |
number | number | min, max |
string | one line, no control characters | max_length (at most 16384 characters) |
text | several lines; tabs and line breaks allowed | max_length (at most 16384 characters) |
enum | one of values | values must not be empty |
list | lines of text | max_items; 16384 characters in all |
duration | whole seconds, 0 or more | max is the most seconds |
item | an item id; stone becomes minecraft:stone | |
block | a block id, read the same way | |
player | a player name: 1 to 16 letters, digits or _ |
title is the form's label (the key when empty), help one line under it, and settings with the
same group are drawn together. A secret setting (a password, a token) is never sent back to a
browser in full. sql_submit reads a database URL only from a secret setting.
The server keeps the values in plugins/<plugin>/settings.json. The plugin reads them with the
settings host call and changes its own with setting_set. When anybody changes them, the
plugins that hear settings_changed are told, except a plugin changing its own.
Pages
A page adds a tab to the web portal. id is [a-z0-9_-]{1,32}, unique in the plugin, and
title is what the tab says. The portal asks for a page over the plugin bus, as a call:
{"op": "portal.page", "page", "viewer"}, and sends a page's actions the same way:
{"op": "portal.action", "page", "action", "values", "viewer"}. The plugin answers
{"components": [...]}: data the portal draws, never markup or script.
The call comes from rustmc:portal, a name no plugin can have, and viewer is the portal user
looking. An answer with more than 200 components, or more than 256 KiB of JSON, is refused, as is
one that is not {"components": [...]}. An answer {"error": "..."} is shown as an error.
Each component is an object with a type. Every string is drawn as text, never as markup.
type | Fields | Drawn as |
|---|---|---|
heading | text | a heading |
text | text | a paragraph |
notice | text, tone? (good or bad) | a status line; bad is drawn as an error |
stats | items: [{label, value}] | a row of cards |
table | columns: [string], rows: [{key?, cells: [value]}], row_actions?: [{action, label, danger?}], empty? | a table; empty (default "Nothing yet.") when there are no rows. A row action sends {key} as its values |
form | action, submit? (default "Save"), fields: [{key, type?, label?, value?, values?}] | a form. A field's type is boolean (a checkbox), enum (a list of values), integer or number; anything else is a text box. Submitting sends the fields' values |
button | action, label, danger? | a button that sends no values |
A component of another type is not drawn. A component that fails to draw is replaced by a
message in its place only. Whatever an action answers replaces the whole page.
The guest SDK's portal module (sdk/src/portal.rs) builds these components.
Switching off vanilla systems
A gamemode that replaces one of the game's own systems names it in disables, and the server
stops running it for as long as the plugin is loaded and alive. A plugin that panics and is
unloaded hands the system back.
| Name | What stops |
|---|---|
hunger | food, saturation and exhaustion stop ticking, and with them starving and the healing food gives. The food bar is the plugin's to set |
mob_spawning | every creature the world makes on its own: the natural spawner, a new chunk's animals, patrols, phantoms, wandering traders, a storm's skeleton trap horse, a portal's zombified piglins, zombie reinforcements, a pearl's endermite, a creaking heart's creaking, a shrieker's warden, villagers' iron golems and raids. What a player, a command, a plugin or a spawner block makes goes on. See below |
natural_regen | the healing a full food bar gives and Peaceful's own, as the natural_health_regeneration game rule turned off does, without changing the rule |
What mob_spawning stops
The rule: switched off, mob_spawning acts as if every game rule that gates the world's own
spawning were off -- spawn_mobs (and with it spawn_monsters's spawns), spawn_patrols,
spawn_phantoms, spawn_wandering_traders, spawn_wardens and raids -- and villagers summon no
iron golems, which have no rule of their own. Everything those rules leave running still runs,
and the two spawner blocks keep their own rules (spawner_blocks_work, and the trial spawner's
spawn_mobs): they are blocks built to spawn, and a gamemode decides whether its world has any.
The game rules themselves are not changed.
Stops:
| Spawn | Why |
|---|---|
| The natural spawner, every category (monsters, animals, water and cave creatures, slimes, drowned) | spawn_mobs |
| A new chunk's packs of animals | spawn_mobs (spawnMobsForChunkGeneration) |
| A thunderstorm's skeleton trap horse | spawn_mobs (tickThunder); the bolt lands as an ordinary strike instead |
| A Nether portal's zombified piglins | spawn_mobs and spawn_monsters (isSpawningMonsters) |
| A hurt zombie's reinforcements | spawn_mobs and spawn_monsters |
| The endermite a landing ender pearl may leave | spawn_mobs and spawn_monsters |
| A creaking heart's creaking | spawn_mobs and spawn_monsters |
| Pillager patrols | spawn_patrols |
| Phantoms | spawn_phantoms |
| The wandering trader and its llamas | spawn_wandering_traders |
| A sculk shrieker's warden (the shrieker answers nobody) | spawn_wardens |
| Raids: none opens, and one under way when the plugin loads is stopped | raids |
| Villagers summoning an iron golem | the village's own population, with no rule |
Goes on:
| Spawn | Why |
|---|---|
Spawn eggs, used or dispensed; /summon; a plugin's spawn calls | asked for by a player, a command or a plugin |
| Breeding, a turtle's eggs and frogspawn hatching, a tadpole growing up, an allay duplicating | a player's doing, or a creature's own offspring |
| A built iron golem, snow golem or wither; a creature from a bucket | built by a player |
| Spawner blocks and trial spawners | blocks built to spawn, with their own rules |
| A structure's fixed creatures (a monument's elder guardians, an igloo's villager) and the End's dragon and crystals | part of what was generated, as with spawn_mobs off |
| A creature changing or dividing: conversions, lightning's transformations, a slime splitting | not a new creature the world chose to add |
A creature's own attack or trap: an evoker's vexes and fangs, a trap horse's riders when a player comes near, a hive's bees, infested and oozing | the creature already exists; spawn_mobs does not stop them either |
The server reads all of these through one helper (spawn::world_spawns), and a test refuses any
other code that reads the spawning rules directly, so a new spawn path cannot forget the switch.
Other names are ignored rather than refused, so a plugin written for a newer server still
loads on an older one. It asks the disabled_systems host call which of its
names were honoured. A server where no plugin asks pays one atomic read per check.
From docs/api/reference/manifest.md in the RustMC repository at bbd81209ad.