Skip to content

Manifest Reference ​

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

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
id✓kebab-case ^[a-z][a-z0-9-]{1,63}$, globally unique. Becomes the namespace for entities (<id>:<suffix>), storage, messages, and asset URLs
name✓display name
version✓semver; treat as immutable once distributed
apiVersion✓must be 2 (the host refuses mismatches at load)
runtime-"public" (default, marketplace/review-gated hosted mods) or "internal" (workspace/first-party mods)
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 mods, Vite-bundled for workspace mods). Public mods 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 mods)
assets-informative; the hosted builder ships the assets/ folder when present (≤ 25 MB)
permissions-array of known capability ids - unknown ids fail validation and every applicable API v2 capability fails closed at runtime
configSchema-JSON-schema-lite for per-world config (below)
messageSchemas-bounded client→server/server→client payload contracts (below); a declared direction is a strict allowlist
components-versioned object schemas for mod-owned runtime entity component data (below)
storageExport-explicit schema/version allowlist for mod storage that may travel in a World Package v2
editorItems-placeable items contributed to the editor Add panel (below)
rendering-client render pipelines the mod supports and its role on the others (below); defaults to classic-only decorative
description, author, license, tags, minHostVersion-metadata for humans and Marketplace listings

rendering ​

VibeLands renders a world through one client pipeline: classic (WebGL, GLSL ShaderMaterial/onBeforeCompile) or node (WebGPU renderer, TSL node materials). Declare which ones your client entry supports:

json
"rendering": { "pipelines": ["classic", "node"], "role": "decorative" }
  • pipelines defaults to ["classic"]. Mods without a client entry work on every pipeline.
  • role: "decorative" (default): on other pipelines the client skips the mod, and its server part keeps running.
  • role: "essential": the world is only offered on pipelines this mod supports.
  • To support both, branch on useRenderPipeline(). vibelands check reports VB_RENDER_PIPELINE_CLASSIC_API or VB_RENDER_PIPELINE_NODE_API when a file uses a pipeline-specific API without branching.

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 useModConfig() (client) and ctx.config (server) return - both sides always see the same validated values.

messageSchemas ​

Tier B mods should declare every network intent and event in the manifest:

json
"messageSchemas": {
  "clientToServer": {
    "openChest": {
      "summary": "Request opening one nearby chest.",
      "delivery": "at-most-once",
      "payload": {
        "type": "object",
        "properties": {
          "chestId": { "type": "string", "minLength": 1, "maxLength": 128 }
        },
        "required": ["chestId"],
        "additionalProperties": false
      }
    }
  },
  "serverToClient": {
    "chestResult": {
      "payload": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" },
          "rewardCents": { "type": "integer", "minimum": 0, "maximum": 1000000 }
        },
        "required": ["ok", "rewardCents"],
        "additionalProperties": false
      }
    }
  }
}

Supported payload types are object, array, string, number, integer, boolean, and null, with bounded properties, required, items, enum, length/count, and numeric limits. $ref, regex, unions, executable validators, and arbitrary schema extensions are intentionally unsupported. Object payloads default to additionalProperties: false.

Once either direction object is present, that direction is a strict allowlist: undeclared names and invalid payloads are blocked in the client SDK and live host. The headless harness throws on the same violation. An absent direction remains open only for API v2 compatibility with older mods.

Client→server declarations may set delivery: "at-most-once" for mutations such as purchases, rewards, claims, and mission transitions. The client SDK then generates an operation id automatically. If application code retries the same intent, it must pass the original id again; a new id means a new intent. The host accepts an id once per authenticated player, mod, and message type within the bounded replay window (currently 5 minutes, at most 2,048 retained ids per mod instance). This suppresses duplicate execution but is not a durable result cache across process restarts.

components ​

Components are schema-versioned data descriptors for this mod's own runtime entities. They do not alter core player, vehicle, or Colyseus schemas:

json
"components": [
  {
    "id": "cargo-state",
    "version": 1,
    "summary": "Replicated cargo securement state.",
    "schema": {
      "type": "object",
      "properties": {
        "secured": { "type": "boolean" },
        "massKg": { "type": "number", "minimum": 0, "maximum": 5000 }
      },
      "required": ["secured"],
      "additionalProperties": false
    }
  }
]

IDs are kebab-case and automatically qualified as mod:<modId>:<componentId> outside the owning entity API. A mod may declare up to 32 descriptors and attach up to 16 values to one entity. Values are validated on spawn/update and share the entity's 2 KB cold-data budget with properties. Increment version when persisted data needs a migration; the v3 draft generator carries descriptor schemas and versions into contracts/state.schema.json.

storageExport ​

Mod storage is private and excluded from world exports by default. Opt in only portable keys, with a schema for every value:

json
"storageExport": {
  "schemaVersion": 1,
  "entries": [
    {
      "key": "world-progress",
      "summary": "Portable world-level activity progress.",
      "schema": {
        "type": "object",
        "properties": { "completed": { "type": "integer", "minimum": 0 } },
        "required": ["completed"],
        "additionalProperties": false
      }
    }
  ]
}

Only listed, schema-valid keys are included. Imports restore them only when the destination resolves the exact mod version, manifest integrity, schema version, and schema integrity. Private keys never enter the package.

Manifest v3 draft ​

API v3 is currently a tooling draft, not a loadable runtime ABI. Creator can project an existing v2 manifest into the draft capability and contract model without changing the production file:

bash
vibelands migrate . --to 3 --dry-run --json

See the Contract Registry for generated files and the command/query/event model.

Generate the shared TypeScript maps and keep them synchronized:

bash
npm run generate
# equivalent: vibelands generate .

The default output is shared/mod-messages.generated.ts. vibelands check fails with a stable diagnostic when that file is missing or stale.

editorItems ​

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

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
id✓kebab-case, unique within the mod; the placed object's preset becomes mod:<modId>:<id>
label✓item card display name
category-any string; known editor categories merge in, new ones get their own filter chip. Defaults to Mods
preset-claim a bare legacy preset id (e.g. tree) instead of the mod: 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 12 pieces; optional XYZ Euler rotation per piece)
physics-host-authoritative placed-object physics (Tier B + physics.colliders + collider required). Supports bodyType, damping, friction/restitution and combine rules, density, CCD, bounded carryable/throwable, and local-+Y powered bounce: { minimumSpeed, maximumSpeed }. Carry, throw, and bounce require bodyType: "dynamic"; a fixed knockdown item may also declare carryable/throwable, which apply once it is knocked loose. A dynamic item may declare trailer (see below)
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. Optional drops (≤ 6 × { "itemId", "quantity"?, "chance" }, chance in (0, 1]) are rolled on depletion; optional skill/level/xp are forwarded to canHarvestResource/onResourceHarvested
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 mod claims the legacy tree/pine/palm presets with colliders and axe-harvestable wood resources.

physics.trailer ​

A dynamic editor item with physics.trailer is a towable trailer. Drivers couple it to a car's tow ball with G; the host owns the ball joint, the wheels (it draws them), suspension, tyres, the parking stand, the cargo bed and hitch failure. Coordinates are item-local at scale 1: +Z along the drawbar towards the car, +Y up, origin on the ground under the axle.

FieldRequiredRules
coupler✓{x, y, z} socket locking onto the tow ball (balls ride about 0.44 m above ground)
axles✓1–3 { z, halfTrack }; each axle carries two wheels
mountY✓strut top-mount height
wheelRadius✓0.1–0.8 m
wheelWidth-0.02–1 m, default 0.18
suspension-{ restLength, maxLength, stiffness (N/m, ≤ 400000), damping (N·s/m, ≤ 60000), maxForce } per wheel
tire-{ corneringStiffness (N/rad), grip (0.05–2), rollingResistance (0–0.3) } per wheel
jack-{x, y, z} parking-stand foot; carries the nose only while uncoupled
cargoBed-{ min, max } box; dynamic props inside it ride with the trailer
hitch-{ attachDistance (≤ 2 m), breakForce (500–400000 N), maxYawDeg, maxRollDeg }
parkingBrake-boolean, default true

trailer requires bodyType: "dynamic" and cannot be combined with carryable or knockdown. Reference: the first-party trailer-kit mod; authoring rules in the repository's docs/TRAILERS.md.

Physical reference: the first-party trampoline mod declares a dynamic, carryable/throwable editor item with a powered bounce surface. The host applies the same energy-bounded rule to players, rigid bodies and vehicles through the physics facade; mod code does not access native bodies.

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 mod is enabled. Core preset and vehicle names always win over a mod item with the same name.

items ​

Inventory items the mod contributes to the host item catalog (up to 128 per mod). 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
id✓lowercase id; may claim a legacy core item id (e.g. wheat) so persisted player inventories keep working. First claimer wins
name✓display 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
tool-{ "weaponId", "harvestMultiplier", "color"? } - harvest upgrade: while carried, hits with that weapon (pickaxe, axe) deal harvestMultiplier× (1–10) damage to resources; the best carried tool wins and its #rrggbb color tints the held tool head for every player
icon-inventory icon: an emoji ("🍯") or an icon-kit glyph tinted by color: ore, lump, pile, gem-rough, gem, orb, ingot, coil, nails, planks, brick, pane, cloth, vial, potion, jar, ring, necklace, amulet, horseshoe, pickaxe, axe. Up to 32 characters; omitted → category icon
color-#rrggbb accent for kit glyphs and the slot tint (tools fall back to tool.color)
rarity-common, uncommon, rare, epic or legendary frame; omitted → derived from the shop value

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

recipes ​

Crafting recipes shown in the inventory's Crafting tab (up to 128 per mod):

json
"recipes": [
  { "id": "smelt-iron", "label": "Iron Bar", "category": "Smelting",
    "station": "furnace", "skill": "smithing", "level": 10, "xp": 36,
    "ingredients": [{ "itemId": "iron_ore", "quantity": 2 }, { "itemId": "charcoal", "quantity": 1 }],
    "output": { "type": "items", "items": [{ "itemId": "iron_bar", "quantity": 1 }] } }
]
FieldRequiredRules
id✓kebab-case, unique in the mod; addressed as <modId>:<id>
label✓display name
category-Crafting-tab filter; defaults to Crafting
ingredients✓1–8 { itemId, quantity }; may name core or other mods' items (hidden when unknown)
output✓{ "type": "items", "items": [...] } (≤ 8, batchable up to 25×) or { "type": "worldObject", "preset", "label"?, "distance"? }
station-one of this mod's editor item ids; the crafter must stand within 4 m of one
skill / level / xp-progression metadata shown in the UI and passed to canCraftRecipe / onRecipeCrafted; the owning mod enforces levels

Reference: the first-party resources mod (62 recipes across smelting, smithing, tools, crafting, jewellery, cooking, herblore and building).

Items render through defineModClient({ objectRenderers: { 'decor-chest': Component } }). Placements are regular persisted world objects - select/move/duplicate/delete work with zero mod code, and the server validates that the owning mod 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 · Runtime API v2