@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
Maintainers
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.gradlenow applies the Kotlin Android plugin. Upstream putskotlin-gradle-pluginon the buildscript classpath and uses akotlin { compilerOptions { ... } }block, but never applies the plugin, so Gradle rejects that block withCould not find method kotlin()before compiling any source. One line.- Renamed to the
@modbenderscope. - iOS support removed entirely (0.4.0): the
ios/sources,Package.swift,CapacitorPlayGames.podspec, andfetchIdentityVerificationSignature(). 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 syncSupported 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
multiplayerpackage contains onlyParticipantEntityandrealtime.RoomEntity— noRealTimeMultiplayerClient,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.
VideosClientandGamesMetadataClientstill ship as interfaces in the artifact, butPlayGames— 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 nativeBufferobject 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, soloadFriends,loadRecentlyPlayedWithPlayers,loadTopScoresandloadPlayerCenteredScorestakepageSize/maxResultsinstead of exposing a cursor.SnapshotsClient.resolveConflict. Manual conflict resolution needs both conflictingSnapshotobjects to survive the same round trip — the same handle-lifetime problem as paging.loadSnapshotandsaveSnapshotinstead expose the SDK's four automaticRESOLUTION_POLICY_*strategies viaconflictPolicy, defaulting to most-recently-modified (last write wins).- Game stats
eventIdanddurationValue. Both exist in the Play Games REST API but not onPlayerGameEvent.Builder, measured directly off the 22.0.0 jar: it has noeventIdsetter (its idempotency-key constructor is non-public) and no duration-typedaddPropertyoverload. Neither can be exposed without a REST path this plugin doesn't have.
API
initialize()signIn(...)isSignedIn()getPlayer()getPlayerId()loadPlayer(...)loadFriends(...)loadRecentlyPlayedWithPlayers(...)showPlayerSearch()showComparePlayer(...)requestServerSideAccess(...)unlockAchievement(...)revealAchievement(...)incrementAchievement(...)setAchievementSteps(...)loadAchievements(...)showAchievements()submitScore(...)showLeaderboard(...)showAllLeaderboards()loadLeaderboards(...)loadLeaderboard(...)loadTopScores(...)loadPlayerCenteredScores(...)loadCurrentPlayerScore(...)loadSnapshot(...)saveSnapshot(...)listSnapshots(...)deleteSnapshot(...)showSnapshots(...)getSnapshotLimits()recordGameEvent(...)recordGameEvents(...)recordProgressUpdate(...)requestGameEventsUpload()incrementEvent(...)loadEvents(...)loadEventsByIds(...)requestRecallAccess()loadPlayerStats(...)addListener('signInStateChanged', ...)removeAllListeners()- Interfaces
- Type Aliases
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
