Writing Functions
Functions are where your game rules live. Each one is a single JavaScript file with a default export. You create and edit them in the Functions tab of your project.
Anatomy
export default async function (ctx, args) {
return { ok: true };
}
ctxgives you access to players, rooms and global state. See the Function API.argsis the object your game passed as the second argument toclient.call().- Whatever you return is sent back to the caller. It must be JSON-serializable.
You can define helper functions and constants in the same file, but you cannot import other modules. Keep everything in one file with a single export default.
Validating input
Never trust args. The client can send anything, so check types and ranges before using them:
export default async function (ctx, args) {
if (typeof args.amount !== "number" || args.amount <= 0 || args.amount > 100) {
throw new NetStackError("BAD_AMOUNT", "Amount must be between 1 and 100.");
}
// ...
}
Rejecting a call
Throw a NetStackError with a code and message. The caller receives both, and nothing your Function changed is kept:
throw new NetStackError("NOT_ENOUGH_GOLD", "You cannot afford that.");
Any other thrown error is reported to the caller as FUNCTION_ERROR without exposing internals.
All-or-nothing changes
Property writes and broadcasts are collected while your Function runs and applied together only if it finishes without throwing. If anything throws, none of it happens. You never need to undo partial changes.
Reading and writing other players
ctx.players lists everyone in the room. Each entry has an id and the same get, set and delete methods as ctx.player:
const target = ctx.players.find((p) => p.id === args.toPlayerId);
if (!target) throw new NetStackError("PLAYER_NOT_IN_ROOM", "That player is not here.");
target.set("gold", (target.get("gold") ?? 0) + args.amount);
Testing
The Test run panel executes your code in the same sandbox as production against sample state you provide. It shows the returned value, every property change, broadcast events, ctx.log output and any error, and it never saves anything or touches live players.
Limits
Each call gets 150 ms of CPU time and 32 MB of memory, with no network, filesystem or timers. See Limits and plans for the full list.
Built-in functions
Every project comes with these Functions already installed. Call them by name from your game; there is nothing to set up.
| Name | What it does |
|---|---|
broadcast |
Best for transporting player movement, chat messages, and more. Sends { from, type, data } to everyone in the room as a broadcast event. |
sendChat |
Validates a message (args.text, up to 200 characters) and sends it to the room as a chat event. |
updatePosition |
Saves the caller's position property and sends a playerMoved event. Takes x, y and optional z. |
getPlayers |
Returns the IDs of everyone in the caller's room. |
setReady |
Marks the caller ready or not ready and sends readyChanged, including whether the whole room is ready. |
getServerTime |
Returns the server clock in milliseconds. |
await client.call("broadcast", { type: "move", data: { x: 12, y: 4 } });
Open a built-in in the Functions tab to read its code or try it in the Test run panel. Press Customize to save your own copy under the same name. Your copy replaces the built-in for that project, and deleting it brings the built-in back. Built-ins do not count toward the 50 Functions per project.
Starter templates
The editor offers templates for using an item, giving an item to another player, and a shared counter. They are a good starting point for common patterns.