Manifest reference

plugin.toml — the four required fields and the capability grants

View as Markdown

Every plugin directory must contain a plugin.toml. The manifest has four required fields and one optional field:

plugin.toml
1name = "vanilla_cat" # required — the plugin's unique name
2version = "0.1.0" # required — informational version string
3entrypoint = "main.star" # required — script path, relative to the plugin dir
4runtime = "starlark" # required — "starlark" or "python"
5capabilities = ["entities.read", "entities.write", "world.read", "nav"] # optional

Required fields

FieldTypeRules
namestringNon-empty. Used for log attribution, unload, and duplicate detection.
versionstringNon-empty.
entrypointstringNon-empty. Must be a relative path inside the plugin directory — absolute paths and any .. segment are rejected (path-traversal guard). The path is cleaned before use.
runtimestring"starlark" or "python". Anything else is rejected loudly (or skipped with a log in the tolerant operator scan).

A manifest missing any required field fails at load with manifest <path>: missing required field "<name>".

Capabilities

capabilities is the plugin’s least-privilege grant list. It defaults to empty — a plugin with no capabilities can subscribe to events, log(), chat(), and register recipe matchers, but it cannot read or mutate any entity, world, or navigation state.

The vocabulary is locked and closed. The seven valid strings:

CapabilityGrants
entities.readReading entity state: position, rotation, health, velocity, type, damage/breeding/fluid flags, attribute(name), the nearest-player-holding scans.
entities.writeMutating an entity: set_velocity, set_attribute, set_look, set_look_at, try_breed, the eat-animation broadcast (plus move_to, which also needs nav).
world.readReading the world: block_at, entities_near, nearest_player, is_dark.
world.writeMutating the world: set_block (and host seams that write blocks, like the sheep’s eat_grass_block).
navIssuing navigation requests: move_to, nav.path_to, nav.stop, nav.jump.
skills.damageDeclaring the skill mechanic("damage", ...) — enforced at load.
skills.effectsDeclaring the skill mechanic("effect", ...) — enforced at load.

Enforcement model

  • Unknown capability strings are rejected at load. A typo like "entitys.read" fails the plugin’s load with unknown capability "entitys.read" (valid: entities.read, entities.write, world.read, world.write, nav, skills.damage, skills.effects) — a capability can never silently grant nothing.
  • Handle operations check per-op at call time. A denied call returns a visible Starlark error the plugin author sees — capability denied: this plugin lacks "world.write" — never a silent no-op. See Handles & capabilities.
  • Skill mechanics check at load. Because skills are pure data known at load time, a mechanic("damage") in a plugin whose manifest lacks skills.damage is a load error (fail-closed), not a runtime denial. See Skills.

Internally the grants are a bit-set (capSet) stamped onto every handle the plugin receives, so each check is a single AND at the operation boundary.

Choosing capabilities: a real example

The bundled vanilla_pig plugin reads its own state and the nearest player, and writes its look and navigation targets — nothing else:

plugins/mobs/vanilla_pig/plugin.toml
1name = "vanilla_pig"
2version = "0.1.0"
3entrypoint = "main.star"
4runtime = "starlark"
5# Least-privilege caps: a pig READS its own state + the nearest player + WRITES its look/nav.
6# It NEVER sets blocks, so NO world.write.
7# entities.read — read x/y/z (stroll offset base, look target delta)
8# entities.write — set_look_at (the LOOK goals' LookControl.setLookAt analogue)
9# world.read — nearest_player (LookAtPlayerGoal's getNearestPlayer)
10# nav — path_to/has_path (WaterAvoidingRandomStrollGoal's navigation.moveTo/isDone)
11capabilities = ["entities.read", "entities.write", "world.read", "nav"]

An event-only plugin needs no capabilities at all:

plugins/gate_events/plugin.toml
1name = "gate_events"
2version = "0.1.0"
3entrypoint = "main.star"
4runtime = "starlark"
5# NO capabilities: gate_events only READS the discrete event payload (frozen scalars) and
6# reacts via the chat() host builtin. It sets no blocks, spawns nothing, reads no world handle.
7capabilities = []