Getting started
Install
npm install platform-achievementsNode.js 22, 24 and 26 are supported (>=22, a maintained-line policy). ESM and CommonJS are both shipped.
1. Describe the achievements
import { resolveAchievements } from 'platform-achievements'
export const achievements = resolveAchievements(
[
{ key: 'first-win', name: 'First Win', description: 'Win your first match.', points: 5 },
{
key: 'ten-wins',
name: 'Ten Wins',
description: 'Win 10 matches, in any mode.',
points: 10,
steps: 10,
},
{
key: 'flawless',
name: 'Flawless',
description: 'Win a match without taking damage.',
points: 15,
hidden: true,
},
],
{ gameCenterNamespace: 'com.example.game' },
)A key is permanent: acknowledgements are stored under it, and ids on the platforms derive from it.
gameCenterNamespace is the prefix of the derived Game Center ids (com.example.game.first_win).
Game Center ids cannot be changed after release, so choose both once.
None of the three platforms has tiers. A game with bronze, silver and gold versions of an achievement
defines three achievements, such as speedrun:bronze, speedrun:silver and speedrun:gold.
2. Check the list against the consoles' limits
import { validateAchievements } from 'platform-achievements'
const issues = validateAchievements(achievements)
// [] when every console will accept the list; otherwise { key, platform, message } per problemPut this in a unit test.
3. Choose an adapter
import { gameServicesAchievements } from 'platform-achievements/game-services'
import { steamAchievements } from 'platform-achievements/steam'
import { webAchievements } from 'platform-achievements/web'
const adapter =
platform === 'android' ? gameServicesAchievements(gameServicesPlugin, 'play')
: platform === 'ios' ? gameServicesAchievements(gameServicesPlugin, 'game-center')
: platform === 'desktop' ? steamAchievements(steamClient) // null when Steam is not running
: webAchievementsgameServicesPlugin is the object your Capacitor game-services plugin exports. steamClient is the
initialised steamworks.js client. Each adapter lists the methods it needs as an interface, so a
test passes a plain object with those methods.
4. Keep acknowledgements
Implement AcknowledgementStore over your save data or database:
import type { AcknowledgementStore } from 'platform-achievements'
const store: AcknowledgementStore = {
durable: true, // acknowledgements survive a restart
acks: (platform) => load(platform), // { 'first-win': 1, 'ten-wins': 7 }
ack: (platform, key, value) => saveMax(platform, key, value), // never lower a stored value
}MemoryAcknowledgementStore is the same interface in memory. It is not durable, so Play Games and
Game Center are sent unlocks only, never progress: without a durable record of what was already
added, an add-only API could count the same steps twice.
5. Sync
import { syncAchievements } from 'platform-achievements'
const result = await syncAchievements({
achievements,
earned, // { 'first-win': 1, 'ten-wins': 7 } from your save data
adapter,
store,
})Call it on sign-in, on app resume and after earning something, as often as you like; log
result.errors. Run one sync per platform at a time: two overlapping syncs would both read the same
acknowledgements and could send the same report twice.
6. Generate the console files
import { submission } from 'platform-achievements/export'
const { files, icons } = submission(achievements, { locale: 'en-US' })See Platforms for what each console imports and how to bring the Play Console's
generated ids back into resolveAchievements.