triggair
Guides

Player accounts and login

Optional email/password login on top of anonymous-first, so a player keeps one identity across devices.

Anonymous-first is the default and it is enough for most games: every device gets a player automatically. Player accounts add a DURABLE identity on top, so a player can sign in and reach the SAME player from any device. It is opt-in per game and never a wall: anonymous play keeps working, and a player only sees a login if you show one. Enable it in the dashboard (Setup → Keys → Player accounts); until you do, the SDK's tg.auth calls report no providers.

How it fits: a credential layer, not a new data model

The account only proves who the player is. Your game data (saves, inventory, stats) still lives under the same per-game player, reached by the same player token every SDK call already uses. Signing in just tells Triggair which player this device is. Accounts are per game: the same login gives a player their Sable Cipher player in Sable Cipher and their Verge Rush player in Verge Rush, and the two never see each other's data.

await tg.auth.providers();   // ['password'] when enabled, [] when off
const { needsConfirmation } = await tg.auth.signUp(email, password);  // sends a confirmation email
const r = await tg.auth.signInWithPassword(email, password);

First sign-in links the current anonymous player

When a player who has been playing anonymously signs in for the first time, their current progress is LINKED to the new account (outcome: 'linked') — nothing is lost, no copy happens. On another device, signing in returns that same player (outcome: 'resumed' or 'adopted'). A brand-new sign-in with no local progress just creates the account's player (outcome: 'created').

The one conflict, and how the merge resolves it

There is exactly one case that needs a decision: the account already has progress AND the current device has its own anonymous progress. Then outcome is 'conflict' and the response carries a merge choice. keep_account keeps the account's data; use_anonymous replaces it with THIS device's progress. Either way the losing player row is PARKED, never deleted, so support can restore it. Show the player the choice; do not silently discard.

const r = await tg.auth.signInWithPassword(email, password);
if (r.outcome === 'conflict') {
  // r.merge tells you both players exist; ask the player which to keep
  await tg.auth.resolveMerge('keep_account');   // or 'use_anonymous'
}
tg.auth.onIdentityChanged(() => refetchEverything());  // the active player id may have changed

Sign out, reset, and what to build

signOut() clears the session and rotates the device to a FRESH anonymous player (so a shared device does not leave the account reachable). sendPasswordReset(email) emails a reset link. All you build is a form: collect an email + password, call signUp / signInWithPassword, and handle the merge prompt on conflict.

await tg.auth.sendPasswordReset(email);
await tg.auth.signOut();   // back to a fresh anonymous identity

Sign in with Google

Enable Google per game in the same panel; then tg.auth.signInWithGoogle() runs the whole OAuth flow in a popup and resolves exactly like signInWithPassword (same outcomes, same merge). Call it from a click handler (browsers block popups opened without a user gesture). It uses one Triggair-hosted callback, so you never register a redirect URI per game. providers() tells you which are enabled, so you can show the right buttons.

if ((await tg.auth.providers()).includes('google')) {
  loginButton.onclick = async () => {
    const r = await tg.auth.signInWithGoogle();
    if (r.outcome === 'conflict') await tg.auth.resolveMerge('keep_account');
  };
}
More in Getting started

← All guides · Recipes · API reference