@idleflowgames/capacitor-play-games
v0.1.1
Published
Capacitor 8 plugin for Google Play Games Services (Android) and Apple GameKit (iOS): sign-in, achievements, leaderboards, and saved games.
Maintainers
Readme
@idleflowgames/capacitor-play-games
Capacitor 8 plugin for platform games services: Google Play Games Services (PGS v2) on Android and Apple GameKit / Game Center on iOS, with a safe no-op fallback on web. One TypeScript API covers sign-in, achievements, leaderboards, and saved games across both platforms.
Install
npm install @idleflowgames/capacitor-play-games
npx 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. |
| iOS | Apple GameKit / Game Center | Requires the Game Center capability + App Store Connect setup. |
| 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 the platform. Create them in the Google Play Console (Android) and App Store Connect / Game Center (iOS), then pass the matching id at each call site. The two platforms issue different ids for the same logical achievement, so keep a per-platform map in your app.
Android
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.
iOS
Enable the Game Center capability on your app target in Xcode and create the app's achievements / leaderboards in App Store Connect. The system presents the Game Center sign-in UI.
If your app shows an App Tracking Transparency (ATT) prompt, request it before calling
initialize(). iOS suppresses an ATT prompt shown while Game Center's "Welcome back" banner is on screen, andinitialize()is what can trigger that banner.
Usage
import { PlayGames } from "@idleflowgames/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: platformAchievementId });
await PlayGames.submitScore({ leaderboardId: platformLeaderboardId, score: 1234 });
// Cross-device saves:
await PlayGames.saveSnapshot({ name: "main", data: JSON.stringify(state) });
const { snapshot } = await PlayGames.loadSnapshot({ name: "main" });
// 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.
API
initialize()signIn(...)isSignedIn()getPlayer()unlockAchievement(...)incrementAchievement(...)showAchievements()submitScore(...)showLeaderboard(...)showAllLeaderboards()loadSnapshot(...)saveSnapshot(...)listSnapshots()deleteSnapshot(...)addListener('signInStateChanged', ...)removeAllListeners()- Interfaces
- Type Aliases
initialize()
initialize() => Promise<void>Initialize the native games SDK. Idempotent.
On Android this triggers PlayGamesSdk.initialize; on iOS it installs the
GameKit authentication handler. Call once, after any App Tracking
Transparency prompt has resolved, before the other methods.
Since: 0.1.0
signIn(...)
signIn(opts?: { silent?: boolean | undefined; } | undefined) => Promise<SignInResult>Sign in to the platform games service.
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.
On Android and iOS this 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
unlockAchievement(...)
unlockAchievement(opts: { id: string; }) => Promise<void>Unlock an achievement by its platform id (Play Console achievement id on Android, App Store Connect / Game Center id on iOS).
| Param | Type |
| ---------- | ---------------------------- |
| opts | { id: string; } |
Since: 0.1.0
incrementAchievement(...)
incrementAchievement(opts: { id: string; steps: number; }) => Promise<void>Increment a partial (incremental) achievement.
steps is interpreted differently per platform: on Android (PGS) it is a
discrete step count toward the achievement's Play Console step total; on
iOS (GameKit) it is added to percentComplete as percentage points.
Compute a platform-appropriate value (e.g. via Capacitor.getPlatform())
so progress matches on both stores.
| Param | Type |
| ---------- | ------------------------------------------- |
| opts | { id: string; steps: number; } |
Since: 0.1.0
showAchievements()
showAchievements() => Promise<void>Show the platform's native achievements UI.
Since: 0.1.0
submitScore(...)
submitScore(opts: { leaderboardId: string; score: number; }) => Promise<void>Submit a score to a leaderboard by its platform id.
| Param | Type |
| ---------- | ------------------------------------------------------ |
| opts | { leaderboardId: string; score: number; } |
Since: 0.1.0
showLeaderboard(...)
showLeaderboard(opts: { leaderboardId: string; }) => Promise<void>Show the native UI for a single leaderboard.
| Param | Type |
| ---------- | --------------------------------------- |
| opts | { leaderboardId: string; } |
Since: 0.1.0
showAllLeaderboards()
showAllLeaderboards() => Promise<void>Show the native all-leaderboards UI.
Since: 0.1.0
loadSnapshot(...)
loadSnapshot(opts: { name: string; }) => Promise<{ snapshot: Snapshot | null; }>Load a saved-game snapshot by its stable name. Resolves { snapshot: null }
when no snapshot exists for that name.
| Param | Type |
| ---------- | ------------------------------ |
| opts | { name: string; } |
Returns: Promise<{ snapshot: Snapshot | null; }>
Since: 0.1.0
saveSnapshot(...)
saveSnapshot(opts: { name: string; data: string; description?: string; }) => Promise<void>Create or overwrite a saved-game snapshot. Conflicts are auto-resolved by most-recently-modified (last write wins), with no merge.
| Param | Type |
| ---------- | ------------------------------------------------------------------ |
| opts | { name: string; data: string; description?: string; } |
Since: 0.1.0
listSnapshots()
listSnapshots() => Promise<{ snapshots: SnapshotMeta[]; }>List metadata for all of the player's snapshots.
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
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 the platform service 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
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. |
PlayerInfo
A signed-in player's public profile.
| Prop | Type | Description |
| ----------------- | ------------------- | ----------------------------------------------------------------------------- |
| playerId | string | Stable, platform-assigned player id (PGS player id / GameKit gamePlayerID). |
| displayName | string | Display name as shown in Google Play Games / Game Center. |
| avatarUrl | string | URL of the player's avatar image, when the platform exposes one. |
Snapshot
A saved-game snapshot together with its serialized payload.
| Prop | Type | Description |
| ----------------- | ------------------- | ----------------------------------------------------------------------- |
| name | string | Stable unique name the snapshot was saved under. |
| description | string | Human-readable description stored with the snapshot. |
| modifiedAt | number | Last-modified time, in epoch milliseconds. |
| data | string | The serialized save payload as a UTF-8 string (encode binary yourself). |
SnapshotMeta
Snapshot metadata without the payload, as returned by listSnapshots.
| Prop | Type | Description |
| ----------------- | ------------------- | ---------------------------------------------------- |
| name | string | Stable unique name of the snapshot. |
| description | string | Human-readable description stored with the snapshot. |
| modifiedAt | number | Last-modified time, in epoch milliseconds. |
PluginListenerHandle
| Prop | Type |
| ------------ | ----------------------------------------- |
| remove | () => Promise<void> |
Type Aliases
SignInStateChangedEvent
Payload of the signInStateChanged event.
SignInResult
Development
pnpm install
pnpm verify # lint + typecheck + build + pack checkThe TypeScript bridge is built to dist/ (ESM + CJS + types). The native sources
under android/ and ios/ ship in the package and are wired up by npx cap sync.
License
MIT © Idle Flow Games
