Skip to Content
Quickstart

This guide gets you from nothing to two players exchanging messages in a shared room. You need an app key from the Plot dashboard  (the free tier needs no credit card).

1. Install the client

npm install -E @plot/client

One package, no peer dependencies.

2. Join a room

import { Plot } from '@plot/client'; const plot = new Plot({ appKey: 'pl_pub_live_xxx', // from your Plot dashboard playerId: crypto.randomUUID(), }); const room = await plot.join({ roomCode: 'lobby-1' }); room.on('message', ({ from, data }) => console.log(from, data)); room.on('join', ({ playerId }) => console.log('joined:', playerId)); room.on('leave', ({ playerId }) => console.log('left:', playerId)); room.send({ x: 100, y: 200 });

Open the same roomCode in two tabs and each will see the other’s messages and presence events. That is the whole v1a relay surface — no server code required.

Multiple apps? If your account has more than one app, also pass appId: new Plot({ appKey, appId }). The client sends it as the X-Plot-App header so requests route to the right app. With a single app it’s optional.

3. Add server logic (optional)

When you want the server to own state — validating moves, running a tick loop, keeping a score — define a room handler and deploy it from the Handlers page in your dashboard :

import { defineRoom } from '@plot/handler'; export default defineRoom<{ scores: Record<string, number> }>({ initialState: { scores: {} }, onJoin(player, ctx) { ctx.state.scores[player.id] = 0; ctx.broadcast('event', { type: 'joined', id: player.id }); }, onMessage(player, msg, ctx) { if (msg.type === 'point') ctx.state.scores[player.id] += 1; }, });

The client doesn’t change — it still joins and sends. The difference is the server now decides what happens. See Authoritative handlers.

4. Real-time games — one call

For an action game you want client prediction, smooth remote interpolation, and auto-reconnection. Rather than wiring those by hand, plot.play() manages them — you declare which state paths are local (predicted) vs remote (interpolated) and draw in onFrame:

const game = await plot.play({ room: 'lobby-1', handler, // the same defineRoom, replayed locally for prediction predict: [{ path: 'positions.{me}', type: 'vec2' }], // you — predicted + corrected interpolate: [{ path: 'positions.*', type: 'vec2' }], // others — interpolated onFrame: ({ me, local, remote }) => draw(local[`positions.${me}`], remote), }); game.input({ kind: 'move', dx, dy, dt }); // predicted send

Prediction, interpolation, keepalive, reconnect-with-resume, and a 60fps frame loop are all on by default. The full game is the starter example  (a handler plus this one call). See plot.play() for the options.

Next steps

Last updated on