NEXIS
Tutorials

Create a Custom Room Plugin

Add room logic without modifying the data-plane core.

Create a Custom Room Plugin

Nexis supports two plugin paths for room logic:

  • Rust room plugin registration at compile time
  • WASM room plugin loading at runtime

This tutorial focuses on runtime WASM plugins (no data-plane rebuild).

1. Implement plugin behavior

Your plugin receives room message inputs with shape:

{
  "type": "player.move",
  "data": { "x": 10, "y": 4, "seq": 18 }
}

Minimal logic pattern:

  • match on input.type
  • read action payload from input.data
  • return { state, event }

Counter example (Rust):

fn on_message(&self, state: &Value, input: &Value) -> PluginOutput {
    match input.get("type").and_then(Value::as_str) {
        Some("inc") => {
            let by = input
                .get("data")
                .and_then(Value::as_object)
                .and_then(|o| o.get("by"))
                .and_then(Value::as_i64)
                .unwrap_or(1);
            let current = state.get("counter").and_then(Value::as_i64).unwrap_or(0);
            let next = current + by;
            PluginOutput::state_with_event(
                json!({ "counter": next }),
                json!({ "type": "counter.updated", "data": { "counter": next } }),
            )
        }
        _ => PluginOutput::state(state.clone()),
    }
}

2. Build plugin module

Use the reference plugin project:

  • examples/wasm-plugins/counter_rust_plugin

Build output should produce a .wasm artifact.

3. Configure runtime loading

Set env var:

NEXIS_WASM_ROOM_PLUGINS='{"duel_room":"/app/plugins/duel_room.wasm"}'

A plugin room can then be joined with room type duel_room.

4. Connect client and send custom types

const room = await client.joinOrCreate("duel_room", { roomId: "duel_room:arena-1" });
room.send("player.move", { x: 12, y: 4, seq: 10 });
room.send("shoot", { dir: 90, weapon: "smg" });

ABI reference (for advanced/plugin runtime debugging)

Required exports:

  • memory
  • alloc(len: i32) -> i32
  • nexis_initial_state() -> i64

Optional lifecycle hooks:

  • nexis_on_create
  • nexis_on_join
  • nexis_on_message
  • nexis_on_tick
  • nexis_on_leave
  • nexis_on_dispose

Hook return payload JSON:

{
  "state": {},
  "event": {}
}

state and event are optional.

Typed Custom Message Contract

function (: ) {
  if (. === 'player.move') {
    .(.., ..);
  }
}

On this page