npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@modbender/capacitor-play-games

v0.5.0

Published

Capacitor 8 plugin binding the full Google Play Games Services v2 client surface (Android): sign-in, players and friends, achievements, leaderboards, saved games, game stats, legacy events, Recall, and player stats, with a safe no-op web fallback.

Downloads

24,378

Readme

@modbender/capacitor-play-games

Capacitor 8 plugin binding the full Google Play Games Services v2 (PGS v2) client surface on Android, with a safe no-op fallback on web. One TypeScript API covers sign-in, players and friends, achievements, leaderboards, saved games, game stats, legacy events, Recall, and player stats.

About this fork

This is a fork of @idleflowgames/capacitor-play-games 0.2.1, MIT-licensed.

Upstream's GitHub repository — github.com/idleflowgames/capacitor-play-games, the URL its own package.json still points at — returned 404 on 2026-09-08, while the npm package remained published. The source here was recovered from that published tarball rather than forked through GitHub. At recovery, the Kotlin, Swift, Gradle and podspec files shipped in the tarball and were vendored byte-for-byte. The TypeScript layer did not ship — the package carried dist/ only, and its sourcemaps set sourcesContent: false — so src/ here was reconstructed from dist/esm/*.js plus the emitted .d.ts. The declarations retained every doc comment, which made that reconstruction faithful rather than a rewrite.

That claim was checked rather than asserted, at 0.3.0, before any of the changes below: building src/ as reconstructed reproduced upstream's published dist/ exactly — all six emitted files, both the .js and the .d.ts, were identical to the ones in the 0.2.1 tarball once formatting was normalised. That is what established the reconstruction was faithful. It is a historical checkpoint, not a standing guarantee — the 0.4.0 changes below (iOS removed, in particular) mean this tree no longer reproduces upstream's dist/, by design.

Changes from upstream 0.2.1

  • android/build.gradle now applies the Kotlin Android plugin. Upstream puts kotlin-gradle-plugin on the buildscript classpath and uses a kotlin { compilerOptions { ... } } block, but never applies the plugin, so Gradle rejects that block with Could not find method kotlin() before compiling any source. One line.
  • Renamed to the @modbender scope.
  • iOS support removed entirely (0.4.0): the ios/ sources, Package.swift, CapacitorPlayGames.podspec, and fetchIdentityVerificationSignature(). The Swift was inherited from upstream and had never been compiled or run — nothing had been through Xcode. The package is now Android plus a safe web no-op fallback; see the changelog for the full rationale.
  • Bound the rest of the Play Games Services v2 client surface (0.5.0): every one of PlayGames's nine client factories now has at least one method behind it. See "What this plugin deliberately doesn't bind" below for what was considered and left out.

Deliberately unchanged: the Android namespace is still com.idleflowgames.playgames. Keeping it means this tree diffs cleanly against the 0.2.1 tarball, so a reviewer can confirm the Android delta is exactly the one line above. Renaming it is a mechanical follow-up, not a blocker.

Status

Android is verified. The plugin module compiles and assembles against a real Capacitor 8 app — :…-capacitor-play-games:assembleDebug is BUILD SUCCESSFUL on Gradle 8.14.3, Android Gradle Plugin 8.13.0 and JDK 21.

The Gradle fix is confirmed by reproduction rather than by reading: revert that one line, rebuild, and the build fails with exactly the error upstream's open issue reports —

Could not find method kotlin() for arguments [...] on project
':modbender-capacitor-play-games' of type org.gradle.api.Project.

Worth knowing: the module keeps upstream's own buildscript classpath pinning AGP 9.3.1 and Kotlin 2.4.10, which is higher than the consuming app's AGP 8.13.0. That combination was expected to be a second conflict and is not — it resolves and builds green as-is.

The TypeScript builds and typechecks clean on TypeScript 7.0.2.

The original MIT copyright is retained in LICENSE alongside this fork's.

Install

bun add @modbender/capacitor-play-games
bunx cap sync

Supported platforms

| Platform | Backing API | Notes | | -------- | --------------------------------------------------------- | ------------------------------------------------------------ | | Android | Google Play Games Services v2 (play-services-games-v2) | Requires PGS configured in the Google Play Console. | | Web | none | Every method resolves to a safe default (signed out, empty). |

Platform setup

Achievement and leaderboard ids are yours: the plugin takes opaque id strings and passes them straight through to Play Games Services. Create them in the Google Play Console, then pass the matching id at each call site.

Configure Play Games Services v2 in the Google Play Console, then wire your project id into Android resources and the app manifest (see Google's Play Games Services docs):

<!-- android/app/src/main/res/values/games-ids.xml -->
<resources>
  <string name="game_services_project_id" translatable="false">YOUR_PGS_PROJECT_ID</string>
</resources>
<!-- inside <application> in android/app/src/main/AndroidManifest.xml -->
<meta-data
  android:name="com.google.android.gms.games.APP_ID"
  android:value="@string/game_services_project_id" />

google-services.json is not required for PGS v2 on its own; it is only needed if you also wire Firebase.

Publishing takes up to two hours to reach players

Play Games Services configuration — achievements, leaderboards, and the event and game-stats definitions — is published independently of the app itself, and publishing it does not publish the app. Changes take up to two hours to propagate, so publish configuration changes at least two hours before a build that depends on them reaches players, or sign-in and the features built on it can fail. Before the configuration is published, only listed testers and players on enabled release tracks can reach Play Games Services at all, which is the usual explanation for a call that works for the developer and fails for everyone else.

Usage

import { PlayGames } from "@modbender/capacitor-play-games";

await PlayGames.initialize();

const { signedIn } = await PlayGames.signIn(); // silent by default
if (!signedIn) {
  // Force the interactive flow from an explicit user gesture:
  await PlayGames.signIn({ silent: false });
}

await PlayGames.unlockAchievement({ id: achievementId });
const { results } = await PlayGames.submitScore({ leaderboardId, score: 1234 });

// Cross-device saves:
await PlayGames.saveSnapshot({ name: "main", data: JSON.stringify(state) });
const { snapshot } = await PlayGames.loadSnapshot({ name: "main" });

// Game stats: fire-and-forget until you flush the queue.
await PlayGames.recordGameEvent({ name: "levelStart", properties: { level: { int: 4 } } });
await PlayGames.requestGameEventsUpload();

// React to system-driven sign-in changes (e.g. signed out via Settings):
await PlayGames.addListener("signInStateChanged", ({ signedIn }) => {
  // update UI
});

On web every method resolves to a safe default, so gate feature usage behind isSignedIn() rather than platform checks.

Limits

These are Google's documented limits, read from Play Games Services documentation pages rather than from the SDK jar the way the rest of this plugin's behaviour was — call them out as documented, not measured, since the rest of this README is the other way round.

Game stats (recordGameEvent / recordGameEvents / recordProgressUpdate): 30 events per recordGameEvents batch, 25 properties per event, event names and property keys up to 100 characters, string property values up to 1024 characters. All four are enforced natively; a call over any limit rejects before it reaches the SDK.

Achievements: 400 achievements per game; 2,000 points total per game, at most 200 points on any one achievement, and every point value a multiple of 5. XP for an achievement is 100 times its point value. Name up to 100 characters, description up to 500; names and descriptions must be unique within the game, and a new achievement needs a unique icon. Of the three states, revealed is the usual starting point and hidden is meant to be used sparingly.

Leaderboards: 70 leaderboards per game. A leaderboard's scoreOrder is fixed once it's published in the Play Console and cannot change afterwards. Daily scores reset at UTC-7, weekly scores reset at the Saturday/Sunday midnight boundary UTC-7, and all-time scores never reset. Scores are stored as long integers, so the raw number is not always the displayed one: a time score is submitted in milliseconds and a currency score in millionths of the main currency unit. Name up to 100 characters; Google's documentation doesn't state a scoreTag length limit, so none is stated here either.

Saved games: a snapshot's data is capped at 3 MB and its cover image at 800 KB, per Google's Cloud Save guide. getSnapshotLimits returns the SDK's own runtime figures rather than these constants, since a hardcoded value would go stale if Google changed it; the unit those two accessors report is almost certainly bytes but that is not confirmed by any reference this was checked against, so verify the magnitude on a real device before comparing a payload's length against them.

Legacy events (incrementEvent / loadEvents / loadEventsByIds): every event is typed as either a premium-currency source or a premium-currency sink, which is the clearest reason this feature isn't a substitute for game stats. Name up to 100 characters, description up to 500. The SDK batches increments, so the counts loadEvents returns are cumulative rather than fine-grained; Google's documentation gives no maximum number of events per game and no numeric rate limit, so neither is stated here.

What this plugin deliberately doesn't bind

Each of these was checked against the v2 SDK jar rather than skipped by oversight — a considered omission, not a gap waiting to be filled.

  • Multiplayer. The v2 SDK doesn't have it. Its multiplayer package contains only ParticipantEntity and realtime.RoomEntity — no RealTimeMultiplayerClient, TurnBasedMultiplayerClient, or invitation/room API. Google removed multiplayer from Play Games Services v2 entirely; no plugin can bind what the SDK no longer exposes.
  • Video capture and game metadata. VideosClient and GamesMetadataClient still ship as interfaces in the artifact, but PlayGames — the v2 entry point — has no factory method for either. Its nine static factories are the complete list, and neither of these two is on it, so both are unreachable from v2 code, not merely unbound.
  • loadMoreFriends, loadMoreRecentlyPlayedWithPlayers, loadMoreScores. Each takes a live native Buffer object as its paging cursor, and that buffer can't survive a round trip to JS without a handle registry keeping it alive on the native side and a lifetime the JS caller could leak. That's a larger design than paging is worth, so loadFriends, loadRecentlyPlayedWithPlayers, loadTopScores and loadPlayerCenteredScores take pageSize/maxResults instead of exposing a cursor.
  • SnapshotsClient.resolveConflict. Manual conflict resolution needs both conflicting Snapshot objects to survive the same round trip — the same handle-lifetime problem as paging. loadSnapshot and saveSnapshot instead expose the SDK's four automatic RESOLUTION_POLICY_* strategies via conflictPolicy, defaulting to most-recently-modified (last write wins).
  • Game stats eventId and durationValue. Both exist in the Play Games REST API but not on PlayerGameEvent.Builder, measured directly off the 22.0.0 jar: it has no eventId setter (its idempotency-key constructor is non-public) and no duration-typed addProperty overload. Neither can be exposed without a REST path this plugin doesn't have.

API

initialize()

initialize() => Promise<void>

No-op. PlayGamesSdk.initialize runs automatically when the plugin loads, driven by the Capacitor bridge — this call exists only to keep the API symmetric with the web fallback.

Since: 0.1.0


signIn(...)

signIn(opts?: { silent?: boolean | undefined; } | undefined) => Promise<SignInResult>

Sign in to Google Play Games.

silent (default true) attempts auto sign-in with no UI; on most devices this succeeds if the player has previously authenticated this game. Pass silent: false to force the full interactive flow, and only in response to an explicit user gesture.

| Param | Type | | ---------- | ----------------------------------- | | opts | { silent?: boolean; } |

Returns: Promise<SignInResult>

Since: 0.1.0


isSignedIn()

isSignedIn() => Promise<{ signedIn: boolean; }>

Whether a player is currently signed in.

Returns: Promise<{ signedIn: boolean; }>

Since: 0.1.0


getPlayer()

getPlayer() => Promise<PlayerInfo>

Get the signed-in player's profile.

Rejects when no player is signed in. On web (the no-op fallback) it resolves an empty profile (playerId: "").

Returns: Promise<PlayerInfo>

Since: 0.1.0


getPlayerId()

getPlayerId() => Promise<{ playerId: string; }>

Get the signed-in player's id without loading the rest of their profile.

The web fallback resolves an empty playerId.

Returns: Promise<{ playerId: string; }>

Since: 0.5.0


loadPlayer(...)

loadPlayer(opts: { playerId: string; forceReload?: boolean | undefined; }) => Promise<{ player: PlayerInfo | null; stale: boolean; }>

Load another player's profile by their Play Games player id. Resolves { player: null } when the lookup can't find that player.

forceReload (default false) bypasses any cache the SDK is holding. The web fallback resolves { player: null, stale: false }.

| Param | Type | | ---------- | ------------------------------------------------------------ | | opts | { playerId: string; forceReload?: boolean; } |

Returns: Promise<{ player: PlayerInfo | null; stale: boolean; }>

Since: 0.5.0


loadFriends(...)

loadFriends(opts?: { pageSize?: number | undefined; forceReload?: boolean | undefined; resolve?: boolean | undefined; } | undefined) => Promise<FriendsResult>

Load a page of the signed-in player's Play Games friends.

pageSize (default 25) bounds the page. forceReload (default false) bypasses any cache the SDK is holding. resolve (default false) launches the consent UI when friends-list access hasn't been granted yet; without it, that case resolves { friends: [], resolutionRequired: true } rather than rejecting — reading the friends list never puts an account dialog in front of the player unless you opt in with resolve: true.

| Param | Type | | ---------- | -------------------------------------------------------------------------------- | | opts | { pageSize?: number; forceReload?: boolean; resolve?: boolean; } |

Returns: Promise<FriendsResult>

Since: 0.5.0


loadRecentlyPlayedWithPlayers(...)

loadRecentlyPlayedWithPlayers(opts?: { pageSize?: number | undefined; forceReload?: boolean | undefined; } | undefined) => Promise<{ players: PlayerInfo[]; stale: boolean; }>

Load players the signed-in player has recently played a match against.

pageSize (default 25) bounds the page. forceReload (default false) bypasses any cache the SDK is holding.

| Param | Type | | ---------- | ------------------------------------------------------------ | | opts | { pageSize?: number; forceReload?: boolean; } |

Returns: Promise<{ players: PlayerInfo[]; stale: boolean; }>

Since: 0.5.0


showPlayerSearch()

showPlayerSearch() => Promise<{ player: PlayerInfo | null; }>

Show the native player search UI. Resolves { player: null } when the player cancels without picking anyone.

Returns: Promise<{ player: PlayerInfo | null; }>

Since: 0.5.0


showComparePlayer(...)

showComparePlayer(opts: { playerId: string; otherPlayerInGameName?: string | undefined; currentPlayerInGameName?: string | undefined; }) => Promise<void>

Show the native UI comparing the signed-in player's profile against another player's.

otherPlayerInGameName/currentPlayerInGameName are optional in-game display name hints shown alongside each player's Play Games name; supply both or neither.

| Param | Type | | ---------- | -------------------------------------------------------------------------------------------------- | | opts | { playerId: string; otherPlayerInGameName?: string; currentPlayerInGameName?: string; } |

Since: 0.5.0


requestServerSideAccess(...)

requestServerSideAccess(opts: { serverClientId: string; forceRefresh?: boolean | undefined; scopes?: AuthScopeName[] | undefined; }) => Promise<{ authCode: string; grantedScopes?: AuthScopeName[] | undefined; }>

Request a one-time OAuth 2.0 server auth code for the signed-in Play Games player, for a backend to exchange for the AUTHORITATIVE player id (Google Play Games Services v2 GamesSignInClient.requestServerSideAccess).

serverClientId is the OAuth 2.0 web client id backing the game; the code is redeemed against it server-side. forceRefresh (default false) requests a fresh code even if one was recently granted. Passing scopes additionally requests consent for those OAuth scopes; the result's grantedScopes is present only when scopes was passed, and lists what the player actually granted (which can be a subset of what was requested).

The web fallback resolves an empty authCode.

| Param | Type | | ---------- | ---------------------------------------------------------------------------------------------------- | | opts | { serverClientId: string; forceRefresh?: boolean; scopes?: AuthScopeName[]; } |

Returns: Promise<{ authCode: string; grantedScopes?: AuthScopeName[]; }>

Since: 0.2.0


unlockAchievement(...)

unlockAchievement(opts: { id: string; }) => Promise<void>

Unlock an achievement by its Play Console achievement id.

Resolves only once the server has recorded the unlock (the SDK's unlockImmediate).

| Param | Type | | ---------- | ---------------------------- | | opts | { id: string; } |

Since: 0.1.0


revealAchievement(...)

revealAchievement(opts: { id: string; }) => Promise<void>

Reveal a hidden achievement without unlocking it.

Resolves only once the server has recorded the reveal (the SDK's revealImmediate).

| Param | Type | | ---------- | ---------------------------- | | opts | { id: string; } |

Since: 0.5.0


incrementAchievement(...)

incrementAchievement(opts: { id: string; steps: number; }) => Promise<{ unlocked: boolean; }>

Increment a partial (incremental) achievement.

steps is a discrete step count toward the achievement's Play Console step total, and must be greater than 0 — the call rejects otherwise.

Resolves only once the server has recorded the increment (the SDK's incrementImmediate). unlocked is true when this increment caused the achievement to become fully unlocked.

| Param | Type | | ---------- | ------------------------------------------- | | opts | { id: string; steps: number; } |

Returns: Promise<{ unlocked: boolean; }>

Since: 0.1.0


setAchievementSteps(...)

setAchievementSteps(opts: { id: string; steps: number; }) => Promise<{ unlocked: boolean; }>

Set the absolute step count of an incremental achievement, rather than incrementing it by a delta. steps must be greater than 0 — the call rejects otherwise.

Resolves only once the server has recorded the new step count (the SDK's setStepsImmediate). unlocked is true when this call caused the achievement to become fully unlocked.

| Param | Type | | ---------- | ------------------------------------------- | | opts | { id: string; steps: number; } |

Returns: Promise<{ unlocked: boolean; }>

Since: 0.5.0


loadAchievements(...)

loadAchievements(opts?: ForceReloadOptions) => Promise<{ achievements: Achievement[]; stale: boolean; }>

Load the signed-in player's achievements, with their current unlock state and (for incremental achievements) step progress.

forceReload (default false) bypasses any cache the SDK is holding.

| Param | Type | | ---------- | ---------------------------------------------------------------------- | | opts | ForceReloadOptions |

Returns: Promise<{ achievements: Achievement[]; stale: boolean; }>

Since: 0.5.0


showAchievements()

showAchievements() => Promise<void>

Show the native Google Play Games achievements UI.

Since: 0.1.0


submitScore(...)

submitScore(opts: { leaderboardId: string; score: number; scoreTag?: string | undefined; }) => Promise<ScoreSubmissionResult>

Submit a score to a leaderboard by its Play Console leaderboard id.

scoreTag is an optional opaque string (e.g. a replay id) stored alongside the score and returned with it later.

Resolves only once the server has recorded the score (the SDK's submitScoreImmediate); see ScoreSubmissionResult for the shape of the result.

| Param | Type | | ---------- | ---------------------------------------------------------------------- | | opts | { leaderboardId: string; score: number; scoreTag?: string; } |

Returns: Promise<ScoreSubmissionResult>

Since: 0.1.0


showLeaderboard(...)

showLeaderboard(opts: { leaderboardId: string; timeSpan?: LeaderboardTimeSpan | undefined; collection?: LeaderboardCollection | undefined; }) => Promise<void>

Show the native UI for a single leaderboard.

timeSpan and collection preselect which slice of the leaderboard opens; both default to the SDK's own default view when omitted.

| Param | Type | | ---------- | -------------------------------------------------------------------------------------------------- | | opts | { leaderboardId: string; timeSpan?: LeaderboardTimeSpan; collection?: LeaderboardCollection; } |

Since: 0.1.0


showAllLeaderboards()

showAllLeaderboards() => Promise<void>

Show the native all-leaderboards UI.

Since: 0.1.0


loadLeaderboards(...)

loadLeaderboards(opts?: ForceReloadOptions) => Promise<{ leaderboards: Leaderboard[]; stale: boolean; }>

Load metadata for all of the game's leaderboards.

forceReload (default false) bypasses any cache the SDK is holding.

| Param | Type | | ---------- | ---------------------------------------------------------------------- | | opts | ForceReloadOptions |

Returns: Promise<{ leaderboards: Leaderboard[]; stale: boolean; }>

Since: 0.5.0


loadLeaderboard(...)

loadLeaderboard(opts: { leaderboardId: string; forceReload?: boolean | undefined; }) => Promise<{ leaderboard: Leaderboard | null; stale: boolean; }>

Load metadata for a single leaderboard. Resolves { leaderboard: null } when no leaderboard exists for that id.

forceReload (default false) bypasses any cache the SDK is holding.

| Param | Type | | ---------- | -------------------------------------------------------------------- | | opts | { leaderboardId: string; forceReload?: boolean; } |

Returns: Promise<{ leaderboard: Leaderboard | null; stale: boolean; }>

Since: 0.5.0


loadTopScores(...)

loadTopScores(opts: LoadScoresOptions) => Promise<LoadScoresResult>

Load the top scores on a leaderboard.

| Param | Type | | ---------- | ---------------------------------------------------------------------- | | opts | LoadScoresOptions |

Returns: Promise<LoadScoresResult>

Since: 0.5.0


loadPlayerCenteredScores(...)

loadPlayerCenteredScores(opts: LoadScoresOptions) => Promise<LoadScoresResult>

Load the scores immediately above and below the signed-in player's own score on a leaderboard.

| Param | Type | | ---------- | ---------------------------------------------------------------------- | | opts | LoadScoresOptions |

Returns: Promise<LoadScoresResult>

Since: 0.5.0


loadCurrentPlayerScore(...)

loadCurrentPlayerScore(opts: { leaderboardId: string; timeSpan?: LeaderboardTimeSpan | undefined; collection?: LeaderboardCollection | undefined; }) => Promise<{ score: LeaderboardScore | null; stale: boolean; }>

Load the signed-in player's own score on a leaderboard. Resolves { score: null } when the player has no score in that time span/collection.

| Param | Type | | ---------- | -------------------------------------------------------------------------------------------------- | | opts | { leaderboardId: string; timeSpan?: LeaderboardTimeSpan; collection?: LeaderboardCollection; } |

Returns: Promise<{ score: LeaderboardScore | null; stale: boolean; }>

Since: 0.5.0


loadSnapshot(...)

loadSnapshot(opts: { name: string; conflictPolicy?: SnapshotConflictPolicy | undefined; }) => Promise<{ snapshot: Snapshot | null; }>

Load a saved-game snapshot by its stable name. Resolves { snapshot: null } when no snapshot exists for that name.

conflictPolicy (default "mostRecentlyModified") picks the automatic strategy used if this device and another wrote the snapshot before either saw the other's write.

| Param | Type | | ---------- | ----------------------------------------------------------------------------- | | opts | { name: string; conflictPolicy?: SnapshotConflictPolicy; } |

Returns: Promise<{ snapshot: Snapshot | null; }>

Since: 0.1.0


saveSnapshot(...)

saveSnapshot(opts: { name: string; data: string; description?: string | undefined; playedTimeMillis?: number | undefined; progressValue?: number | undefined; coverImage?: string | undefined; conflictPolicy?: SnapshotConflictPolicy | undefined; }) => Promise<void>

Create or overwrite a saved-game snapshot.

playedTimeMillis and progressValue are opaque numbers you define and that surface in the native snapshot-picker UI; coverImage is a base64 PNG/JPEG with no data: URI prefix. conflictPolicy (default "mostRecentlyModified") picks the automatic strategy used if this device and another wrote the snapshot before either saw the other's write — manual conflict resolution isn't exposed, see the README.

| Param | Type | | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | opts | { name: string; data: string; description?: string; playedTimeMillis?: number; progressValue?: number; coverImage?: string; conflictPolicy?: SnapshotConflictPolicy; } |

Since: 0.1.0


listSnapshots(...)

listSnapshots(opts?: ForceReloadOptions) => Promise<{ snapshots: SnapshotMeta[]; }>

List metadata for all of the player's snapshots.

forceReload (default false) bypasses any cache the SDK is holding.

| Param | Type | | ---------- | ---------------------------------------------------------------------- | | opts | ForceReloadOptions |

Returns: Promise<{ snapshots: SnapshotMeta[]; }>

Since: 0.1.0


deleteSnapshot(...)

deleteSnapshot(opts: { name: string; }) => Promise<void>

Delete a saved-game snapshot by its stable name.

| Param | Type | | ---------- | ------------------------------- | | opts | { name: string; } |

Since: 0.1.0


showSnapshots(...)

showSnapshots(opts?: { title?: string | undefined; allowAdd?: boolean | undefined; allowDelete?: boolean | undefined; maxSnapshots?: number | undefined; } | undefined) => Promise<{ snapshot: SnapshotMeta | null; isNew: boolean; }>

Show the native saved-games UI for picking, and optionally creating or deleting, a snapshot. Resolves { snapshot: null } when the player cancels without picking one.

title (default "Saved games") labels the picker; allowAdd and allowDelete (both default true) show or hide those actions; maxSnapshots (default: no limit) caps how many existing snapshots are listed.

| Param | Type | | ---------- | ---------------------------------------------------------------------------------------------------------------- | | opts | { title?: string; allowAdd?: boolean; allowDelete?: boolean; maxSnapshots?: number; } |

Returns: Promise<{ snapshot: SnapshotMeta | null; isNew: boolean; }>

Since: 0.5.0


getSnapshotLimits()

getSnapshotLimits() => Promise<{ maxDataSize: number; maxCoverImageSize: number; }>

Get the platform's per-snapshot size limits, read from the device at call time rather than hardcoded, so they can't go stale if Google changes them. See "Saved games" in the Limits section above for Google's documented figures and an important caveat about the unit these two numbers are in.

The web fallback resolves both as 0. Check isSignedIn() before treating either as a real limit, because comparing a payload length against a zero reports every save as oversized.

Returns: Promise<{ maxDataSize: number; maxCoverImageSize: number; }>

Since: 0.5.0


recordGameEvent(...)

recordGameEvent(opts: { name: string; properties?: GameEventProperties | undefined; }) => Promise<void>

Record one game-stats event, rejecting if it's over the limits in "Game stats" in the Limits section above (event name length, property count, property key/value length) — all enforced natively.

This call is fire-and-forget: the native SDK method returns no result, so a resolved promise means the event was queued on the device, not that Google has received it. Call requestGameEventsUpload to flush the queue; anything not yet uploaded is lost if the app crashes first.

| Param | Type | | ---------- | -------------------------------------------------------------------------- | | opts | { name: string; properties?: GameEventProperties; } |

Since: 0.5.0


recordGameEvents(...)

recordGameEvents(opts: { events: GameEvent[]; }) => Promise<void>

Record a batch of game-stats events in one native call, subject to the same per-event limits as recordGameEvent plus a limit on the batch itself — see "Game stats" in the Limits section above.

Fire-and-forget for the same reason as recordGameEvent: a resolved promise means the batch was queued, not delivered.

| Param | Type | | ---------- | -------------------------------------------------------------------------- | | opts | { events: GameEvent[]; } |

Since: 0.5.0


recordProgressUpdate(...)

recordProgressUpdate(opts: { currentProgress: number; properties?: GameEventProperties | undefined; }) => Promise<void>

Record a game-stats progress-update event: shorthand for recordGameEvent with the fixed event name progressUpdate and an int property currentProgress set to this value, subject to the same limits (see "Game stats" in the Limits section above).

Fire-and-forget for the same reason as recordGameEvent.

| Param | Type | | ---------- | ------------------------------------------------------------------------------------------- | | opts | { currentProgress: number; properties?: GameEventProperties; } |

Since: 0.5.0


requestGameEventsUpload()

requestGameEventsUpload() => Promise<void>

Flush any game-stats events queued by recordGameEvent/recordGameEvents/ recordProgressUpdate to Google's servers. This is the only confirmation point in the game-stats API; nothing else in it reports delivery.

Since: 0.5.0


incrementEvent(...)

incrementEvent(opts: { eventId: string; amount: number; }) => Promise<void>

Increment a legacy Play Console event (not a game-stats event) by amount. Like the native SDK method, this is fire-and-forget: a resolved promise means the request was made, not that the server has recorded it.

| Param | Type | | ---------- | ---------------------------------------------------- | | opts | { eventId: string; amount: number; } |

Since: 0.5.0


loadEvents(...)

loadEvents(opts?: ForceReloadOptions) => Promise<{ events: GameEventDefinition[]; stale: boolean; }>

Load all of the game's legacy Play Console events and the signed-in player's cumulative counts on them.

forceReload (default false) bypasses any cache the SDK is holding.

| Param | Type | | ---------- | ---------------------------------------------------------------------- | | opts | ForceReloadOptions |

Returns: Promise<{ events: GameEventDefinition[]; stale: boolean; }>

Since: 0.5.0


loadEventsByIds(...)

loadEventsByIds(opts: { eventIds: string[]; forceReload?: boolean | undefined; }) => Promise<{ events: GameEventDefinition[]; stale: boolean; }>

Load specific legacy Play Console events by id. Rejects if eventIds is empty.

forceReload (default false) bypasses any cache the SDK is holding.

| Param | Type | | ---------- | ------------------------------------------------------------ | | opts | { eventIds: string[]; forceReload?: boolean; } |

Returns: Promise<{ events: GameEventDefinition[]; stale: boolean; }>

Since: 0.5.0


requestRecallAccess()

requestRecallAccess() => Promise<{ sessionId: string; }>

Request a Play Games Recall session id: an opaque identifier from RecallClient, obtained without requiring the player to sign in to Play Games first.

The web fallback resolves an empty sessionId; don't forward it to a backend as if it were a real session identifier.

Returns: Promise<{ sessionId: string; }>

Since: 0.5.0


loadPlayerStats(...)

loadPlayerStats(opts?: ForceReloadOptions) => Promise<{ stats: PlayerStats; stale: boolean; }>

Load Play Games' engagement and spend predictions for the signed-in player.

forceReload (default false) bypasses any cache the SDK is holding.

| Param | Type | | ---------- | ---------------------------------------------------------------------- | | opts | ForceReloadOptions |

Returns: Promise<{ stats: PlayerStats; stale: boolean; }>

Since: 0.5.0


addListener('signInStateChanged', ...)

addListener(event: "signInStateChanged", listener: (e: SignInStateChangedEvent) => void) => Promise<PluginListenerHandle>

Listen for sign-in state changes: an interactive sign-in completing, or the player signing out of Google Play Games system-wide.

| Param | Type | | -------------- | --------------------------------------------------------------------- | | event | 'signInStateChanged' | | listener | (e: SignInResult) => void |

Returns: Promise<PluginListenerHandle>

Since: 0.1.0


removeAllListeners()

removeAllListeners() => Promise<void>

Remove all listeners registered through this plugin.

Since: 0.1.0


Interfaces

PlayerInfo

A player's public profile.

| Prop | Type | Description | | ------------------------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------------- | | playerId | string | Stable, platform-assigned Play Games player id. | | displayName | string | Display name as shown in Google Play Games. | | avatarUrl | string | Small avatar URL, absent when the player has no icon image. | | hiResImageUrl | string | Full-resolution avatar URL, absent when the player has no hi-res image. | | bannerImageLandscapeUrl | string | Landscape profile banner URL, absent when the player has none. | | bannerImagePortraitUrl | string | Portrait profile banner URL, absent when the player has none. | | title | string | Play Games title such as "Expert", absent when unset. | | retrievedAt | number | Epoch ms this profile was fetched, absent when the SDK doesn't report one. | | lastPlayedWithAt | number | Epoch ms this player was last played with, absent when unknown. | | level | PlayerLevelInfo | Player level and XP progress, absent when the game has no level configuration. | | friendStatus | PlayerFriendStatus | Relationship to the signed-in player, absent when unknown. | | friendsListVisibility | FriendsListVisibility | Present only on the signed-in player's own profile. |

PlayerLevelInfo

A player's level and XP progress within the game's configured level ladder.

| Prop | Type | Description | | -------------------- | ------------------------------------------------------ | ----------------------------------------------------------------------------- | | currentXpTotal | number | Absent when the SDK reports its CURRENT_XP_UNKNOWN sentinel. | | lastLevelUpAt | number | Epoch ms of the player's most recent level-up, absent when they haven't leveled up. | | isMaxLevel | boolean | Whether the player has reached the highest configured level. | | currentLevel | PlayerLevel | The level the player is currently in. | | nextLevel | PlayerLevel | The next level up; equal to currentLevel when isMaxLevel is true. |

PlayerLevel

One level's XP boundaries in the game's configured level ladder.

| Prop | Type | | ------------------- | ---------------------- | | levelNumber | number | | minXp | number | | maxXp | number |

SignInResult

Result of a sign-in attempt, or the payload of a sign-in state change.

| Prop | Type | Description | | -------------- | -------------------------------------------------- | ----------------------------------------------------------- | | signedIn | boolean | Whether the player is currently authenticated. | | player | PlayerInfo | The player profile, present only when signedIn is true. |

FriendsResult

Result of loadFriends.

| Prop | Type | Description | | ------------------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ | | friends | PlayerInfo[] | The requested page of friends, empty when resolutionRequired is true. | | stale | boolean | Whether this result may already be out of date on the server. | | resolutionRequired | boolean | True when the player has not granted friends-list access. friends is empty; call loadFriends again with resolve: true to show the consent UI. |

StandardAchievement

A single-unlock achievement's Play Console metadata plus the signed-in player's progress on it.

| Prop | Type | Description | | ----------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------------- | | id | string | Play Console achievement id. | | type | 'standard' | | | name | string | | | description | string | | | state | AchievementState | | | xpValue | number | XP granted by unlocking this achievement. | | lastUpdatedAt | number | Epoch ms this achievement's state last changed. | | unlockedImageUrl | string | Icon shown once unlocked, absent when the SDK reports none. | | revealedImageUrl | string | Icon shown while hidden or revealed, absent when the SDK reports none. |

IncrementalAchievement

A stepped-counter achievement's Play Console metadata plus the signed-in player's step progress on it.

| Prop | Type | Description | | ---------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------------- | | id | string | Play Console achievement id. | | type | 'incremental' | | | name | string | | | description | string | | | state | AchievementState | | | xpValue | number | XP granted by fully unlocking this achievement. | | lastUpdatedAt | number | Epoch ms this achievement's state last changed. | | unlockedImageUrl | string | Icon shown once unlocked, absent when the SDK reports none. | | revealedImageUrl | string | Icon shown while hidden or revealed, absent when the SDK reports none. | | currentSteps | number | | | totalSteps | number | | | formattedCurrentSteps | string | | | formattedTotalSteps | string | |

Leaderboard

A leaderboard's Play Console metadata.

| Prop | Type | Description | | ------------------ | ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | | leaderboardId| string | | | displayName | string | | | iconImageUrl | string | Absent when the SDK reports none. | | scoreOrder | LeaderboardScoreOrder | Play Console policy, not an SDK guarantee — see "Leaderboards" in the Limits section above. | | variants | LeaderboardVariant[] | One entry per time-span/collection combination the SDK reports. |

LeaderboardVariant

One time-span/collection slice of a leaderboard, with the signed-in player's standing in it.

| Prop | Type | Description | | ------------------------ | -------------------------------------------------------------------- | ---------------------------------------------------------------------- | | timeSpan | LeaderboardTimeSpan | | | collection | LeaderboardCollection | | | hasPlayerInfo | boolean | Whether the signed-in player has a score in this variant. | | playerScore | number | Absent when the SDK reports its PLAYER_SCORE_UNKNOWN sentinel. | | displayPlayerScore| string | Absent under the same condition as playerScore. | | playerRank | number | Absent when the SDK reports its PLAYER_RANK_UNKNOWN sentinel. | | displayPlayerRank | string | Absent under the same condition as playerRank. | | playerScoreTag | string | Absent when the signed-in player has no score tag in this variant.| | numScores | number | Absent when the SDK reports its NUM_SCORES_UNKNOWN sentinel. |

LeaderboardScore

One entry in a leaderboard's score list.

| Prop | Type | Description | | ------------------------------ | ---------------------------------------------------- | ------------------------------------------------------------ | | rank | number | Absent when the SDK reports its LEADERBOARD_RANK_UNKNOWN sentinel. | | displayRank | string | | | rawScore | number | | | displayScore | string | | | achievedAt | number | Epoch ms this score was recorded. | | scoreTag | string | | | scoreHolder | PlayerInfo | The player who holds this score, absent when the SDK doesn't attach one. | | scoreHolderDisplayName | string | | | scoreHolderIconImageUrl | string | Absent when the score holder has no icon image. | | scoreHolderHiResImageUrl| string | Absent when the score holder has no hi-res image. |

LoadScoresOptions

Options shared by loadTopScores and loadPlayerCenteredScores.

| Prop | Type | Description | | ------------------- | ---------------------------------------------------------------------- | ------------------------------------------------------ | | leaderboardId| string | | | timeSpan | LeaderboardTimeSpan | Defaults to "allTime". | | collection | LeaderboardCollection| Defaults to "public". | | maxResults | number | Defaults to 25. Values outside 1-25 are not validated by this plugin, and whether the SDK clamps or rejects an out-of-range value was not confirmed. | | forceReload | boolean | Defaults to false. |

ForceReloadOptions

Options accepted by read methods whose only option is bypassing the SDK's cache.

| Prop | Type | Description | | ------------------ | ----------------------- | ------------------------ | | forceReload | boolean | Defaults to false. |

LoadScoresResult

Result of loadTopScores and loadPlayerCenteredScores.

| Prop | Type | Description | | ------------------ | ------------------------------------------------------ | ------------------------------------------------------------ | | leaderboard | Leaderboard | null | The leaderboard's own metadata, absent when the SDK doesn't attach it. | | scores | LeaderboardScore[] | | | stale | boolean | |

ScoreSubmissionResult

Result of a leaderboard score submission.

| Prop | Type | Description | | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | | leaderboardId| string | | | playerId | string | | | results | { timeSpan: LeaderboardTimeSpan; rawScore: number; formattedScore: string; scoreTag: string; newBest: boolean; }[] | One entry per time span the submission affected. The SDK does not guarantee all three (daily/weekly/all-time) are present, and does not report why a given one might be missing. |

SnapshotMeta

Snapshot metadata without the payload, as returned by listSnapshots and showSnapshots.

| Prop | Type | Description | | ---------------------------- | --------------------- | ----------------------------------------------------------------------------- | | name | string | Stable unique name the snapshot was saved under. | | snapshotId | string | Platform-assigned snapshot id, distinct from name. | | description | string | | | modifiedAt | number | Last-modified time, in epoch milliseconds. | | playedTimeMillis | number | Absent when the SDK reports its PLAYED_TIME_UNKNOWN sentinel. | | progressValue | number | Absent when the SDK reports its PROGRESS_VALUE_UNKNOWN sentinel. | | deviceName | string | Name of the device the snapshot was last written from, absent when unknown. | | coverImageUrl | string | Absent when the snapshot has no cover image. | | coverImageAspectRatio| number | Absent under the same condition as coverImageUrl. | | hasChangePending | boolean | Whether a change to this snapshot is pending sync to Google's servers. |

Snapshot

A saved-game snapshot together with its serialized payload.

| Prop | Type | Description | | ----------- | --------------------- | ----------------------------------------------------------------------- | | data | string | The serialized save payload as a UTF-8 string (encode binary yourself). |

Snapshot extends SnapshotMeta — see that entry for name, snapshotId, description, modifiedAt, playedTimeMillis, progressValue, deviceName, coverImageUrl, coverImageAspectRatio and hasChangePending.

GameEvent

One game-stats event to rec