App keys
A client authenticates to Plot with a publishable app key (pl_pub_live_…)
from your dashboard. The key identifies your app and is safe to ship in client
code — it authorizes only the player handshake (/v1/connect), never writes.
Developer tooling uses a separate developer key (pl_sk_…), a secret
revealed in the dashboard. The plot CLI and the developer asset + AI write APIs
require it; because it can create, overwrite, and delete your app’s data, it must
never be shipped to clients. (Custom player JWTs are signed with your app’s JWT
secret — a third, distinct credential; see Player identity.)
Player identity
The simplest setup is anonymous players — you supply any stable playerId:
const plot = new Plot({ appKey, playerId: crypto.randomUUID() });To tie Plot identity to your own accounts, issue a custom player JWT signed with your app’s JWT secret (HS256) and pass it instead:
const plot = new Plot({ appKey, playerToken });The server verifies the signature and trusts the sub claim as the player id,
so players can’t impersonate one another.
Either a playerId or a playerToken is required — construct Plot with one or
the other. When a playerToken is supplied its sub claim becomes the
authoritative player id.
Reconnection
Network drops happen. A single resume window governs what a returning player
gets back: the room holds their slot (defers onLeave) and issues a short-lived
reconnect token, both valid for the same duration. A reconnect inside the
window resumes the slot in place — onJoin is not re-run and the player keeps
their in-room state — and the token returns them to the same room without
re-running matchmaking.
The window is tier-derived:
| Tier | Resume window |
|---|---|
| Free | 30 seconds |
| Starter | 2 minutes |
| Pro / Studio | 5 minutes |
A longer window lets a player drop off Wi-Fi, background the app, or reload the page and still return to their exact seat. The reconnect token is bound to the player and app and expires with the slot, so it can never resume a seat the room has already released.
room.on('disconnect', () => {/* the client retries with backoff automatically */});The SDK retries with exponential backoff by default (250 ms doubling to a 30 s
cap, ~90 s of attempts). If the resume window has elapsed by the time the player
returns, they rejoin as a fresh session and the handler’s onJoin runs again.
Session resume
Reconnection alone survives a dropped socket; session resume additionally survives a full page reload. When enabled, the SDK persists the last authoritative snapshot, any unacked predicted inputs (the outbox), and the reconnect token to a durable store — so a reload picks up where it left off, replaying pending inputs and resuming the same slot rather than starting cold.
It is on by default. Two options control it:
PlotOptions.store?: LocalStore— the backing store. Defaults tolocalStoragewhen available, falling back to an in-memory store otherwise.JoinOptions.resume?: boolean(defaulttrue) — setfalseto opt out and always join as a fresh session.
const plot = new Plot({ appKey, playerId });
const room = await plot.join({ room: 'LOBBY1' }); // resume: true by defaultThe session is keyed per app/room/player and is cleared when the player leaves intentionally. The server’s snapshot always stays authoritative, so a stale cache is corrected on the next update.