Skip to content

Steam Achievements

Unlock and query achievements.

Loading Stats

IMPORTANT

Before reading or writing stats, you must wait for Steam to load the stats data from the server. Call requestStatsAndWait() once at game start.

javascript
// At game start, after steam.init()
const statsReady = await steam.requestStatsAndWait(3000); // Wait up to 3 seconds

if (statsReady) {
  console.log('Stats loaded, ready to use achievements and stats!');
} else {
  console.warn('Could not load stats from Steam');
}

Stats State

You can check the current stats loading state:

javascript
const state = await steam.getStatsState();
// 0 = not requested
// 1 = pending (waiting for Steam)
// 2 = ready (can read/write stats)
// 3 = failed

// Or use the helper:
const ready = await steam.areStatsReady(); // true/false

Methods

unlockAchievement(name)

Unlock an achievement.

javascript
await steam.unlockAchievement('ACH_WIN_FIRST_GAME');
await steam.storeStats(); // Required to save

getAchievement(name)

Check if achievement is unlocked.

javascript
const unlocked = await steam.getAchievement('ACH_WIN_FIRST_GAME');
// true or false

clearAchievement(name)

Clear an achievement (for testing).

javascript
await steam.clearAchievement('ACH_WIN_FIRST_GAME');
await steam.storeStats();

storeStats()

Save achievements and stats to Steam.

javascript
await steam.storeStats();

WARNING

Always call storeStats() after modifying achievements or stats.

Stats

Stat names must match your App Admin schema — exactly

If setStatInt / setStatFloat return false even though getStatInt works, the cause is almost always your Steamworks App Admin configuration. Check all of the following:

  1. The stat exists in your App Admin — Steamworks Partner site → Application → Stats & Achievements → Stats.
  2. The name matches 1:1 — it is case-sensitive. TOTAL_KILLS, total_kills, and Total_Kills are three different stats. Copy/paste the name from App Admin into your code.
  3. The type matches. A stat configured as INT will refuse setStatFloat(), and vice-versa. AVGRATE stats use setStatFloat().
  4. The stat is published. New stats sit as a draft until you click "Publish" in the Steamworks dashboard. Until then they don't exist for any client — including yours.
  5. The value is inside [MinValue, MaxValue] if you configured a range in App Admin. Out-of-range writes silently fail.

If something still doesn't work, open stats_log.txt from your Steam client — Valve prints the exact reason there:

  • macOS: ~/Library/Application Support/Steam/logs/stats_log.txt
  • Linux: ~/.local/share/Steam/logs/stats_log.txt (or ~/.steam/steam/logs/stats_log.txt)
  • Windows: <Steam install>\logs\stats_log.txt

setStatInt(name, value)

Set an integer stat.

javascript
await steam.setStatInt('TOTAL_KILLS', 100);
await steam.storeStats();

getStatInt(name)

Get an integer stat. Returns 0 if stats are not loaded yet.

javascript
const kills = await steam.getStatInt('TOTAL_KILLS');

TIP

Make sure to call requestStatsAndWait() at game start before reading stats.

setStatFloat(name, value)

Set a float stat.

javascript
await steam.setStatFloat('TOTAL_DISTANCE', 1234.56);
await steam.storeStats();

getStatFloat(name)

Get a float stat. Returns 0 if stats are not loaded yet.

javascript
const distance = await steam.getStatFloat('TOTAL_DISTANCE');

requestStats()

Request stats from Steam (async, returns immediately).

javascript
await steam.requestStats();

requestStatsAndWait(timeoutMs)

Request stats and wait until they're loaded. Returns true if successful.

javascript
const ready = await steam.requestStatsAndWait(3000); // 3 second timeout

areStatsReady()

Check if stats have been loaded.

javascript
if (await steam.areStatsReady()) {
  // Safe to read/write stats
}

Examples

Basic Achievement

javascript
async function onBossDefeated(bossName) {
  // Achievement for first boss
  if (bossName === 'Dragon') {
    await steam.unlockAchievement('ACH_DEFEAT_DRAGON');
    await steam.storeStats();
  }
}

Progress Achievement

javascript
// At game start
async function initGame() {
  // Wait for stats to load from Steam
  const ready = await steam.requestStatsAndWait(3000);
  if (!ready) {
    console.warn('Stats not available');
  }
}

async function onEnemyKilled() {
  // Stats are already loaded, safe to read
  const kills = await steam.getStatInt('TOTAL_KILLS') || 0;
  await steam.setStatInt('TOTAL_KILLS', kills + 1);
  
  // Achievement at 100 kills
  if (kills + 1 >= 100) {
    await steam.unlockAchievement('ACH_100_KILLS');
  }
  
  await steam.storeStats();
}

Track Distance

javascript
let sessionDistance = 0;

function onMove(delta) {
  sessionDistance += delta;
}

async function onGameEnd() {
  const total = await steam.getStatFloat('TOTAL_DISTANCE') || 0;
  await steam.setStatFloat('TOTAL_DISTANCE', total + sessionDistance);
  
  // Achievement for 10km
  if (total + sessionDistance >= 10000) {
    await steam.unlockAchievement('ACH_MARATHON');
  }
  
  await steam.storeStats();
  sessionDistance = 0;
}

Multiple Achievements

javascript
const ACHIEVEMENTS = {
  firstWin: 'ACH_FIRST_WIN',
  perfectLevel: 'ACH_PERFECT',
  speedrun: 'ACH_SPEEDRUN',
  collector: 'ACH_ALL_ITEMS'
};

async function checkAchievements(gameState) {
  if (gameState.wins >= 1) {
    await steam.unlockAchievement(ACHIEVEMENTS.firstWin);
  }
  
  if (gameState.perfectLevels >= 1) {
    await steam.unlockAchievement(ACHIEVEMENTS.perfectLevel);
  }
  
  if (gameState.fastestTime < 60) {
    await steam.unlockAchievement(ACHIEVEMENTS.speedrun);
  }
  
  if (gameState.items.length >= 50) {
    await steam.unlockAchievement(ACHIEVEMENTS.collector);
  }
  
  await steam.storeStats();
}

Steamworks Dashboard

Define achievements in your Steamworks dashboard:

  1. Go to Steamworks Partner
  2. Select your app
  3. Navigate to Stats & Achievements
  4. Add achievements with API names
  5. Upload icons (64x64 locked, 64x64 unlocked)