CrowdyJS best practices
Two clients, two tokens
Build an identity client (session token) and a per-game client (app-scoped token). Gameplay surfaces reject the session token.
const identity = createCrowdyClient({ httpUrl, tokenStore: sessionStore });
await identity.auth.login({ email, password });
const appToken = await identity.portal.mintAppToken(appId);
const game = createCrowdyClient({
httpUrl: appToken.gameApiUrl ?? httpUrl,
wsUrl: appToken.gameApiWsUrl ?? wsUrl,
tokenStore: appStore,
});
game.setToken(appToken.token);
Do not set managementUrl. There is one GraphQL origin.
A game on your own domain uses hosted Studio sign-in
(client.portal.signIn), not auth.login from the page.
Follow the app's endpoint
Use gameApiUrl / gameApiWsUrl from mintAppToken or
game.serverStatus.gameClientBootstrap(appId). Keep the shared origin as
discoveryUrl for recovery. See
Shared environment routing and
Datacenter routing.
Pass httpUrl and wsUrl explicitly so an SDK default never silently
points a sandbox build at production.
Server code on ck-exec
Drive gameplay rules through your app's ck-exec hubs, which a
game calls through client.exec. The browser presents confirmed state; it
does not decide damage, captures, or team assignment locally. A call that is
refused throws a CrowdyExecError whose status names why (AppError,
Denied, …); retryable is true for the statuses worth trying again with
backoff.
See Game API best practices and connect from a game.
Credentials
- Do not embed a
manage_appssession or an org token in the public game bundle. - Refresh the app token before
expiresAt. - Studio-admin surfaces belong in a trusted admin tool, not the shipped player client.