Skip to content

Manifest Reference

vibelands-plugin.json at the plugin root. Validated by vibelands check, by the server at discovery (invalid manifests are logged and skipped), and by the SDK's validatePluginManifest.

Full example

json
{
  "id": "ambient-butterflies",
  "name": "Ambient Butterflies",
  "version": "1.2.0",
  "apiVersion": 2,
  "runtime": "public",
  "tier": "A",
  "description": "Butterflies that drift over flowers during the day.",
  "author": { "name": "Dev", "url": "https://example.dev" },
  "license": "MIT",
  "entries": {
    "client": "client/index.jsx"
  },
  "assets": "assets/",
  "permissions": ["audio"],
  "configSchema": {
    "type": "object",
    "properties": {
      "density": { "type": "number", "default": 1, "minimum": 0, "maximum": 3 }
    }
  },
  "editorItems": [
    { "id": "butterfly-marker", "label": "Butterfly Marker", "category": "Nature" }
  ],
  "tags": ["ambient", "decorative"]
}

Fields

FieldRequiredRules
idkebab-case ^[a-z][a-z0-9-]{1,63}$, globally unique. Becomes the namespace for entities (<id>:<suffix>), storage, messages, and asset URLs
namedisplay name
versionsemver; treat as immutable once distributed
apiVersionmust be 2 (the host refuses mismatches at load)
runtime-"public" (default, marketplace/review-gated hosted plugins) or "internal" (workspace/first-party plugins)
tier"A" (client-only decorative) or "B" (server-authoritative). Tier B requires entries.server
entries.clientpublicpath to the React/R3F client source entry (built to dist/client.js for hosted plugins, Vite-bundled for workspace plugins). Public plugins require this field
entries.servertier Bpath to the server source entry (built to dist/server.js, CJS, Node 20, isolated worker target for hosted public plugins)
assets-informative; the hosted builder ships the assets/ folder when present (≤ 25 MB)
permissions-array of known permission ids - unknown ids fail validation, undeclared capabilities are denied at runtime
configSchema-JSON-schema-lite for per-world config (below)
editorItems-placeable items contributed to the editor Add panel (below)
description, author, license, tags, minHostVersion-metadata for humans and Marketplace listings

configSchema

A deliberately small subset of JSON Schema - enough for validated config and an auto-generated admin form:

json
{
  "type": "object",
  "properties": {
    "density":  { "type": "number",  "default": 1,      "minimum": 0, "maximum": 3 },
    "rounds":   { "type": "integer", "default": 3,      "minimum": 1, "maximum": 10 },
    "color":    { "type": "string",  "default": "#7ad7ff" },
    "nightOnly":{ "type": "boolean", "default": true },
    "mode":     { "type": "string",  "enum": ["calm", "wild"], "default": "calm" }
  }
}

Resolution rules (applied by the host before your code sees the config):

  1. wrong-typed values → replaced by default
  2. numbers → clamped to minimum/maximum; integer → rounded
  3. values outside enum → replaced by default
  4. unknown keys → dropped

The resolved object is what usePluginConfig() (client) and ctx.config (server) return - both sides always see the same validated values.

editorItems

Items the plugin contributes to the in-game editor's Add library (up to 32 per plugin):

json
"editorItems": [
  {
    "id": "decor-chest",
    "label": "Decorative Chest",
    "category": "Treasure",
    "description": "A purely decorative chest.",
    "properties": { "title": "Chest", "color": "#7a4a21" },
    "shadows": true,
    "propertySchema": {
      "type": "object",
      "properties": {
        "title": { "type": "string", "title": "Title", "maxLength": 64 }
      }
    },
    "scale": 1
  }
]
FieldRequiredRules
idkebab-case, unique within the plugin; the placed object's preset becomes plugin:<pluginId>:<id>
labelitem card display name
category-any string; known editor categories merge in, new ones get their own filter chip. Defaults to Plugins
preset-claim a bare legacy preset id (e.g. tree) instead of the plugin: namespace - existing DB objects with that preset render/behave through this item. First claimer wins; conflicts are logged and skipped
collider-blocking collider for placed objects (server physics + client): { "type": "ball", "radius", "offsetY" }, { "type": "cuboid", "halfExtents", "offsetY" } or { "type": "compound", "pieces": [{ "halfExtents", "offset" }] } (max 8 pieces)
resource-makes the item harvestable: { "kind", "requiredWeaponId", "itemId", "maxHealth", "reward", "respawnMs" }. kind: "tree" gets the host fall-and-stump presentation; itemId must exist in the inventory catalog
shadows-set false for invisible or transparent editor volumes that should not cast or receive host shadows. Defaults to true
properties-default properties merged into every placement (placer values win); ≤ 2 KB
propertySchema-host-rendered per-object fields persisted in properties; supports string, number, integer, boolean, enum, limits, descriptions, and multiline strings
scale-uniform default scale, positive number

Reference: the first-party forest plugin claims the legacy tree/pine/palm presets with colliders and axe-harvestable wood resources.

Editor items are also part of the in-game AI command vocabulary: players with edit rights can say spawn 10 beacons (matching the item's label or id) and the host spawns the item with its default properties and scale, exactly like an editor placement - but only in worlds where the plugin is enabled. Core preset and vehicle names always win over a plugin item with the same name.

items

Inventory items the plugin contributes to the host item catalog (up to 32 per plugin). They flow through the host inventory - stacking, persistence, phone UI, General Store - exactly like core items:

json
"items": [
  { "id": "wheat_seed", "name": "Wheat Seed", "category": "Seed",
    "maxStack": 99, "shop": { "buyCents": 180 } },
  { "id": "wheat", "name": "Wheat", "category": "Crop",
    "maxStack": 999, "shop": { "sellCents": 120 } }
]
FieldRequiredRules
idlowercase id; may claim a legacy core item id (e.g. wheat) so persisted player inventories keep working. First claimer wins
namedisplay name
category-inventory grouping label; defaults to Item
maxStack-1–9999, defaults to 99
effects-applied by the host when the player uses the item: health/hunger/thirst/energy deltas
shop-{ "buyCents", "sellCents" } - buyable and/or sellable at the General Store

Reference: the first-party farming plugin (seeds + crops).

Items render through definePluginClient({ objectRenderers: { 'decor-chest': Component } }). Placements are regular persisted world objects - select/move/duplicate/delete work with zero plugin code, and the server validates that the owning plugin is enabled and declares the item. Optional server hooks: onEditorObjectPlaced(ctx, event) / onEditorObjectRemoved(ctx, event).

The built artifact manifest

The hosted sandbox builder writes a copy of the manifest into the signed artifact with an added integrity block (sha384 per entry). The server recomputes hashes from hosted artifact files at load time - the artifact values are for registries and humans, not trust.

VibeLands Creator - Plugin SDK apiVersion 2