Skip to Content
Game ServerRemote Function

Remote Function

server.remoteFunction(name, args?, options?) calls a method on your Server class from the client. args is an array, mapped positionally onto the method’s parameters. There are three modes:

ModeOptionResolves withRate limitServer failure visible?Use for
Standard—the method’s return value10 calls/s per function nameyes — the promise rejectsanything whose result or failure matters
Fire-and-forgetneedResponse: falsenull, once the call is queued10 calls/s per function namenocosmetic or continuously re-sent updates
Throttledthrottle: <ms>null, immediatelynonenohigh-frequency streams such as position
⚠️

The rate limit is counted on the client: the 11th call to the same function name within a rolling second rejects with Too many calls to the function. Use throttle option. before anything is sent. Each function name has its own budget.

Standard Calls

const { connected, server } = useGameServer(); if (!connected) return "Connecting..."; const handleCalculate = async () => { const result = await server.remoteFunction("add", [1, 2]); console.log(result); // 3 };

Standard calls run in order. Each settles exactly once — on the server’s reply, on a disconnect, or after a 30-second timeout. A rejection is an Error carrying two extra fields:

  • code — machine-readable cause, e.g. TIMEOUT, DISCONNECTED, ISOLATE_DIED
  • retryable — whether retrying the same call can succeed
try { await server.remoteFunction("buyItem", ["sword"]); } catch (e) { if (e.retryable) retryLater(); else showError(e.message); // e.code tells you which failure it was }

Fire-and-forget Calls

With needResponse: false no reply is awaited: a failure on the server, or a socket that drops before the queue flushes, is invisible to the caller. Never use it for anything whose loss a player would notice.

server.remoteFunction("updateMyNickname", [{ nick: "karl" }], { needResponse: false, });

Throttled Calls

For real-time updates where intermediate values can be skipped, pass throttle in milliseconds (values below 30 are raised to 30). needResponse is forced to false, so a throttled call never reports a server-side failure; it rejects only when the socket is already down, which is what the .catch(() => {}) absorbs.

const handleMouseMove = (e) => { server .remoteFunction("updateMyPosition", [{ x: e.clientX, y: e.clientY }], { throttle: 50, }) .catch(() => {}); };
⚠️

Throttling drops the LAST call of a burst, not just the middle ones. The throttle runs on the leading edge only, so the first call in each window goes through and everything after it is discarded — including the final one that carries the state you actually want to land. For a drag that ends when the player lets go, the resting position is exactly what gets dropped.

Either opt into the trailing edge with throttleTrailing: true, or send one unthrottled call at the end of the interaction. Only rely on plain throttling for values you re-send continuously anyway, such as a per-frame position in a loop that never stops.

The throttle window is keyed by function name. To throttle several entities through one function independently, give each its own throttleKey:

server .remoteFunction("updateBall", [{ ballId: 1, position }], { throttle: 50, throttleKey: "1" }) .catch(() => {}); server .remoteFunction("updateBall", [{ ballId: 2, position }], { throttle: 50, throttleKey: "2" }) .catch(() => {});

Reserved Names

  • joinRoom and leaveRoom never reach the server: remoteFunction() rejects them before sending, with <name>() is no longer available via remoteFunction(). Use server.<name>() directly. A server function with either name is registered but unreachable, so call a matchmaker findMatch or createRoom, and enter the room with joinRoom(roomId) from useGameServer().
  • Names beginning $on, $roomTick or $room are refused by the server with Reserved function: <name> — and with needResponse: false the call is dropped without any reply, so it simply vanishes. Keep your own function names clear of these prefixes.

Connection Status

connected is a boolean: the client either reached the server or it did not. if (!connected) return "Connecting..." is fine for a prototype, but in a shipped game a player staring at “Connecting…” cannot tell a cold-starting server from a full region from a game that is simply broken. useGameServer() also returns a reactive connectionStatus — { phase, reason?, retryAfterMs? } — that says which it is.

phaseMeaning
idleNot connected and not trying — initial, or after disconnect()
connectingFirst attempt; never connected in this session
connectedLive
reconnectingWas connected, dropped, retrying
unavailableGave up — a terminal failure, or the retry deadline passed

reason is a machine-readable cause. Common values while waiting are 'not_ready' (the server is starting up), 'at_capacity' (the region is busy), 'draining' (the server instance you were routed to is shutting down — reconnecting picks a fresh one) and 'reserving' (another allocation holds the reservation); 'rate_limited' also shows up here, when the verse is at its connection quota. Terminal reasons such as 'recovering' — the verse is in a broken state after a crash and the SDK has given up — arrive with phase === 'unavailable' instead of while connecting. After a socket close, reason carries the close reason string instead. retryAfterMs is the server’s suggested wait before the next attempt — use it for a countdown.

You do not have to drive reconnection yourself: the SDK retries automatically and honors the server’s suggested timing. The only action connectionStatus asks of you is a retry button on unavailable — and that button must call disconnect() before connect(), because a terminal close (duplicate login, verse stopped, admin kick, auth failure) latches the connection shut and a bare connect() only logs a warning and returns.

const { connectionStatus: cs, connect, disconnect } = useGameServer(); if (cs.phase === "connected") return <Game />; if (cs.phase === "unavailable") { const retry = () => { disconnect(); connect(); }; return <button onClick={retry}>Unavailable — Retry</button>; } // connecting / reconnecting return ( <div> {cs.reason === "not_ready" ? `Starting server… ${Math.ceil((cs.retryAfterMs ?? 0) / 1000)}s` : "Connecting…"} </div> );

For finer classification of a raw close code, the SDK also exports isTerminalCloseCode / isTransientCloseCode.

Last updated on