Adding a new Node Type
A node is one activity inside a workshop — a slide, a quiz, a whiteboard. Each one exists twice: as a class in the Nakama runtime that owns the state, and as a React component that renders it. This page walks through both halves.
For troubleshooting go to "help".
The lifecycle
workshop.ts drives the match loop and calls into the current node. DefaultNode
defines five hooks; override the ones you need and ignore the rest.
| Hook | Called when |
|---|---|
init | the node is entered, including when the host navigates backwards (goBack) |
loop | every tick — 5× per second |
messages | a client sends data for this node |
matchSignal | an external signal arrives (LLM result, agent update) |
sendData | node state should be broadcast to clients |
= function, never with method syntaxDefaultNode assigns these as instance properties, not prototype methods:
init = function (this: DefaultNode, _workshop, _state, _goBack) {};
A subclass that declares init(...) { } in normal method syntax is silently
ignored — the property assigned in the constructor shadows the prototype method,
so your code never runs and nothing warns you. Every existing node
(QuizNode, ChatNode, ScoreboardNode, GuessItNode, …) uses = function.
The leading this: DefaultNode is a TypeScript this parameter, not an
argument. It gives you typed access to this.data, this.autoNext and friends;
callers do not pass it.
The hooks mutate state and this.data in place. They return nothing — do
not return state.
Backend (Nakama)
1. Extend the NodeType enum
apps/nakama/src/models/workshop_models.ts
export enum NodeType {
Slide = 'Slide',
Quiz = 'Quiz',
Whiteboard = 'Whiteboard',
YourNewNode = 'YourNewNode', // add yours
}
The frontend has its own copy of this enum in apps/web/src/models/workshop.ts.
Both must agree — the string value is the contract.
2. Implement the node
apps/nakama/src/workshop/nodes/YourNewNode.ts
class YourNewNode extends DefaultNode {
constructor(
node: WorkshopNode_Default,
state: workshop_State,
ctx: nkruntime.Context
) {
super(node, state, ctx);
}
init = function (
this: DefaultNode,
workshop: workshopObject,
state: workshop_State,
goBack: boolean
) {
let nodeState = this.data as YourNewNodeState;
// Set up node state. `goBack` is true when the host navigated backwards,
// which usually means restoring a later step instead of starting over.
nodeState.index = goBack ? 1 : 0;
};
messages = function (
this: DefaultNode,
workshop: workshopObject,
state: workshop_State,
message: nkruntime.MatchMessage
) {
// Read message.data, update this.data, then broadcast:
this.sendData(workshop, state);
};
}
Keep the runtime constraint in mind: this code compiles to ES5. No for...of
over non-arrays, no Map/Set iteration, no generators, no Symbol. Use
Object.keys() and plain array methods.
3. Register it in the factory
apps/nakama/src/workshop/workshop_Info.ts
case NodeType.YourNewNode:
tempNodeClass = new YourNewNode(tempNode, state, ctx);
break;
Without this the node falls back to DefaultNode and simply does nothing.
Frontend (Next.js)
1. Extend the enum
apps/web/src/models/workshop.ts — same string value as the backend.
2. Create the component
apps/web/src/components/nodes/YourNewNode.tsx
Components receive the workshop payload and the node payload as props, and send
data back through sendDataToNode:
export default function YourNewNode(props: {
workshop: WorkshopPayload;
node: NodePayload;
sendDataToNode: (opcode: number, data: object) => void;
setNavButtons: (buttons: NavButtons) => void;
}) {
const nodeData = props.node.nodeData as YourNewNodeData;
return props.workshop.isHost ? (
<div>{/* host view */}</div>
) : (
<div>{/* participant view */}</div>
);
}
Look at an existing node of similar shape before writing a new one —
ImgQuizNode for host/participant splits, ChatNode for streaming updates.
3. Map it in the workshop page
apps/web/src/app/workshop/[id]/page.tsx
case NodeType.YourNewNode:
return <YourNewNode key={index} {...nodeProps} />;
Opcodes
If your node needs new client↔server messages, add them to the OpCode enum —
which exists twice, in apps/nakama/src/models/workshop_models.ts and
mirrored in apps/web/src/models/. Numbers must match; a mismatch shows up as
messages silently going nowhere.
Testing
Add a workshop JSON under apps/web/workshops/ that contains your node, then:
pnpm run dev
Open http://localhost:3000/StoragePush to push the workshop into Nakama storage, create a session and step into your node.
For a preview deployment the same applies, except the accounts are already seeded — see Deployment.