Steamworks SDK
Integrate Valve's Steamworks SDK for achievements, leaderboards, Cloud saves, Workshop modding, multiplayer lobbies, DLC ownership checks, and SteamPipe depot builds on PC.
When to Use
Use this skill when the task involves any of the following Steamworks features:
- Achievements & stats: unlocking, incrementing, and storing per-user stats via
ISteamUserStats.
- Leaderboards: global/friend score tables, upload and download.
- Steam Cloud: cross-machine save sync via Remote Storage or Auto-Cloud.
- Workshop / UGC: publishing, subscribing to, and loading mods via
ISteamUGC.
- Lobbies & multiplayer: matchmaking lobbies, P2P, and Game Networking Sockets.
- DLC & ownership: gating content by
IsDlcInstalled / ownership queries.
- Rich presence, overlay, depots/builds via SteamPipe.
Do Not Use
| If the task is… |
Use instead |
| iOS/Android in-app purchases or store submission |
game-mobile-store-integration |
| Web/server payments, subscriptions |
stripe-integration |
| Console (Switch/PS/Xbox) cert & online |
game-console-porting-certification |
| Godot networking high-level API only |
game-godot-multiplayer-networking |
Prerequisites
- A Steam App ID issued by Valve (requires a paid Steam Direct app).
- The Steamworks SDK downloaded from the partner site and linked into the project (headers +
steam_api64.lib / steam_api64.dll on Windows).
- The Steam client running and logged in for local development testing.
- Achievements, stats, and leaderboards defined on the Steamworks partner site before the API can reference them.
steamcmd installed for SteamPipe build uploads (download from Valve's SteamPipe docs).
Procedure
1. Setup Contract
- Obtain an App ID from Valve (paid Steam Direct app).
- Place
steam_appid.txt (containing only the App ID as plain text) next to the executable for development only — never ship it; the launched-from-Steam client provides the App ID in production.
- Call
SteamAPI_Init() early in startup; if it fails, the game was not launched through Steam (or steam_appid.txt / running client is missing) — handle gracefully.
- Call
SteamAPI_RunCallbacks() every frame, and SteamAPI_Shutdown() on exit.
if (!SteamAPI_Init()) {
// Not launched via Steam, or Steam client not running.
// Fail soft: disable Steam features, don't crash.
}
// per frame:
SteamAPI_RunCallbacks(); // REQUIRED — without it, no callbacks fire
// on exit:
SteamAPI_Shutdown();
Hard rule: steam_appid.txt must be excluded from all shipping/release builds. Shipping it lets the game run without ownership checks.
2. Achievements & Stats
// SDK 1.61+ requests the current user's stats automatically at startup
// (RequestCurrentStats was removed). On older SDKs, call
// SteamUserStats()->RequestCurrentStats() and wait for UserStatsReceived.
// Unlock + push to server (StoreStats is what actually persists/displays).
SteamUserStats()->SetAchievement("ACH_FIRST_BLOOD");
SteamUserStats()->SetStat("enemies_killed", killCount);
SteamUserStats()->StoreStats(); // batch then store once, not per-kill
Steps:
- Define every achievement and stat on the Steamworks partner site first; the API only references IDs that already exist there. Undefined IDs cause silent no-ops.
- Batch
SetStat / SetAchievement calls, then call StoreStats() once — calling StoreStats() per event is rate-limited and slow.
- For testing, use
ClearAchievement() and ResetAllStats(true) to reset progress.
3. Leaderboards
// Find-or-create, then upload. Both are async (SteamCall + callback).
SteamAPICall_t h = SteamUserStats()->FindOrCreateLeaderboard(
"HighScores", k_ELeaderboardSortMethodDescending,
k_ELeaderboardDisplayTypeNumeric);
// in the callback, with the handle:
SteamUserStats()->UploadLeaderboardScore(
leaderboard, k_ELeaderboardUploadScoreMethodKeepBest, score, nullptr, 0);
- Use
KeepBest for high-score tables.
- Use
ForceUpdate only when the latest value must always win (e.g. fastest current time where lower is better but semantics differ from a simple max).
4. Steam Cloud Saves
- Simplest path: enable Auto-Cloud in the partner site (map file globs to Cloud) — zero code, but no conflict logic.
- Code path: use
ISteamRemoteStorage::FileWrite / FileRead with a quota check.
- Hard rule: handle the sync conflict case (two machines, newer save) explicitly; never blind-overwrite. Blind overwrites lose progress on multi-PC users.
- Keep saves small and versioned; Cloud has per-user quota.
5. Workshop (UGC)
// Create an item, then update its content/preview, then submit.
SteamAPICall_t c = SteamUGC()->CreateItem(appId, k_EWorkshopFileTypeCommunity);
// in callback (PublishedFileId_t id):
UGCUpdateHandle_t u = SteamUGC()->StartItemUpdate(appId, id);
SteamUGC()->SetItemContent(u, "C:/mod/content");
SteamUGC()->SetItemPreview(u, "C:/mod/preview.png");
SteamUGC()->SubmitItemUpdate(u, "initial version");
For consuming mods:
- Call
GetSubscribedItems to enumerate subscribed items.
- Call
GetItemInstallInfo to get the local folder path.
- Load mod content from disk.
- Never assume a mod folder exists; users unsubscribe mid-session. Always check validity before loading.
6. Lobbies & Networking
- Matchmaking lobbies (
ISteamMatchmaking::CreateLobby, RequestLobbyList, lobby data key/values) for grouping players and exchanging connection metadata.
- For transport, prefer Steam Game Networking Sockets (
ISteamNetworkingSockets) over the legacy P2P API — it provides NAT punch-through, relays (SDR), and encryption.
- Lobby data is the meeting point; actual gameplay traffic goes over the sockets connection negotiated via the lobby.
Hard rule: do not use the legacy P2P API for new projects. Use Game Networking Sockets for NAT traversal + encryption.
7. DLC & Ownership
- Gate DLC content with
SteamApps()->IsDlcInstalled(dlcAppId).
- Centralize the App ID; mismatches break ownership and stats queries.
8. SteamPipe Builds
| Concept |
Meaning |
| Depot |
A bucket of files (e.g. per-OS, per-DLC) |
| App build |
A set of depot snapshots published to a branch |
| Branch |
default (live) or beta branches (password-gated) |
Upload via steamcmd + a app_build_*.vdf script (or the Web/Steamworks UI).
Hard rule: always publish to a beta branch first, validate, then promote to default.
Example PowerShell command to upload a build:
steamcmd +login <build_account> +run_app_build /path/to/app_build_480.vdf +quit
Replace <build_account> with your Steamworks build account (never hardcode live credentials in scripts).
Pitfalls
- Shipping
steam_appid.txt: lets the game run without ownership checks — remove it from release builds.
- Never calling
RunCallbacks(): every async result (stats, leaderboards, UGC) silently never returns.
StoreStats() per event: rate-limited and slow. Batch SetStat/SetAchievement, then StoreStats() once.
- Using achievements that aren't defined on the partner site: API calls no-op silently.
- Blind Cloud overwrite: loses progress on multi-PC users. Implement conflict handling.
- Assuming Steam is always present:
SteamAPI_Init can fail (offline, no client). Degrade gracefully, never hard-crash.
- Legacy P2P for new projects: use Game Networking Sockets for NAT traversal + encryption.
- Hardcoding the App ID in many places: centralize it; mismatches break ownership/stats.
Verification
Related Skills
game-mobile-store-integration — the mobile-store equivalent (IAP, store services).
game-console-porting-certification — console online/cert when porting beyond PC.
game-godot-multiplayer-networking / game-unreal-engine — engine-side networking these lobbies feed into.
References
- Steamworks SDK documentation: Stats & Achievements, Leaderboards, Remote Storage, UGC, Matchmaking, Game Networking Sockets, SteamPipe — available on the Steamworks partner site.
- Load
references/ files when deeper API surface detail is needed (e.g. callback structures, VDF schema examples). Check the references/ directory for supplementary docs before writing complex integration code.
1---2name: game-steamworks-sdk3description: Use when integrating the Steamworks SDK into a PC game — Steam achievements and stats, leaderboards, Steam Cloud saves, Workshop (UGC) modding, matchmaking lobbies and P2P, rich presence, DLC ownership checks, and depot/build uploads via SteamPipe. Triggers on Steamworks, ISteamUserStats, ISteamRemoteStorage, ISteamUGC, ISteamMatchmaking, app_id, steam_appid.txt, SteamPipe, achievements, leaderboards, Workshop. Not for mobile App Store/Play IAP (use game-mobile-store-integration), Stripe/web payments (use stripe-integration), or console certification (use game-console-porting-certification).4---5
6# Steamworks SDK
7
8Integrate Valve's Steamworks SDK for achievements, leaderboards, Cloud saves, Workshop modding, multiplayer lobbies, DLC ownership checks, and SteamPipe depot builds on PC.
9
10## When to Use
11
12Use this skill when the task involves any of the following Steamworks features:
13
14- **Achievements & stats**: unlocking, incrementing, and storing per-user stats via `ISteamUserStats`.
15- **Leaderboards**: global/friend score tables, upload and download.
16- **Steam Cloud**: cross-machine save sync via Remote Storage or Auto-Cloud.
17- **Workshop / UGC**: publishing, subscribing to, and loading mods via `ISteamUGC`.
18- **Lobbies & multiplayer**: matchmaking lobbies, P2P, and Game Networking Sockets.
19- **DLC & ownership**: gating content by `IsDlcInstalled` / ownership queries.
20- **Rich presence, overlay, depots/builds** via SteamPipe.
21
22### Do Not Use
23
24| If the task is… | Use instead |
25|---|---|
26| iOS/Android in-app purchases or store submission | `game-mobile-store-integration` |
27| Web/server payments, subscriptions | `stripe-integration` |
28| Console (Switch/PS/Xbox) cert & online | `game-console-porting-certification` |
29| Godot networking high-level API only | `game-godot-multiplayer-networking` |
30
31## Prerequisites
32
331. A **Steam App ID** issued by Valve (requires a paid Steam Direct app).
342. The **Steamworks SDK** downloaded from the partner site and linked into the project (headers + `steam_api64.lib` / `steam_api64.dll` on Windows).
353. The **Steam client** running and logged in for local development testing.
364. Achievements, stats, and leaderboards **defined on the Steamworks partner site** before the API can reference them.
375. `steamcmd` installed for SteamPipe build uploads (download from Valve's SteamPipe docs).
38
39## Procedure
40
41### 1. Setup Contract
42
431. Obtain an **App ID** from Valve (paid Steam Direct app).
442. Place `steam_appid.txt` (containing only the App ID as plain text) next to the executable **for development only** — never ship it; the launched-from-Steam client provides the App ID in production.
453. Call `SteamAPI_Init()` early in startup; if it fails, the game was not launched through Steam (or `steam_appid.txt` / running client is missing) — handle gracefully.
464. Call `SteamAPI_RunCallbacks()` **every frame**, and `SteamAPI_Shutdown()` on exit.
47
48```cpp
49if (!SteamAPI_Init()) {
50 // Not launched via Steam, or Steam client not running.
51 // Fail soft: disable Steam features, don't crash.
52}
53// per frame:
54SteamAPI_RunCallbacks(); // REQUIRED — without it, no callbacks fire
55// on exit:
56SteamAPI_Shutdown();
57```
58
59**Hard rule**: `steam_appid.txt` must be excluded from all shipping/release builds. Shipping it lets the game run without ownership checks.
60
61### 2. Achievements & Stats
62
63```cpp
64// SDK 1.61+ requests the current user's stats automatically at startup
65// (RequestCurrentStats was removed). On older SDKs, call
66// SteamUserStats()->RequestCurrentStats() and wait for UserStatsReceived.
67
68// Unlock + push to server (StoreStats is what actually persists/displays).
69SteamUserStats()->SetAchievement("ACH_FIRST_BLOOD");
70SteamUserStats()->SetStat("enemies_killed", killCount);
71SteamUserStats()->StoreStats(); // batch then store once, not per-kill
72```
73
74Steps:
751. Define every achievement and stat on the **Steamworks partner site first**; the API only references IDs that already exist there. Undefined IDs cause silent no-ops.
762. Batch `SetStat` / `SetAchievement` calls, then call `StoreStats()` once — calling `StoreStats()` per event is rate-limited and slow.
773. For testing, use `ClearAchievement()` and `ResetAllStats(true)` to reset progress.
78
79### 3. Leaderboards
80
81```cpp
82// Find-or-create, then upload. Both are async (SteamCall + callback).
83SteamAPICall_t h = SteamUserStats()->FindOrCreateLeaderboard(
84 "HighScores", k_ELeaderboardSortMethodDescending,
85 k_ELeaderboardDisplayTypeNumeric);
86// in the callback, with the handle:
87SteamUserStats()->UploadLeaderboardScore(
88 leaderboard, k_ELeaderboardUploadScoreMethodKeepBest, score, nullptr, 0);
89```
90
91- Use `KeepBest` for high-score tables.
92- Use `ForceUpdate` only when the latest value must always win (e.g. fastest current time where lower is better but semantics differ from a simple max).
93
94### 4. Steam Cloud Saves
95
96- **Simplest path**: enable **Auto-Cloud** in the partner site (map file globs to Cloud) — zero code, but no conflict logic.
97- **Code path**: use `ISteamRemoteStorage::FileWrite` / `FileRead` with a quota check.
98- **Hard rule**: handle the **sync conflict** case (two machines, newer save) explicitly; never blind-overwrite. Blind overwrites lose progress on multi-PC users.
99- Keep saves small and versioned; Cloud has per-user quota.
100
101### 5. Workshop (UGC)
102
103```cpp
104// Create an item, then update its content/preview, then submit.
105SteamAPICall_t c = SteamUGC()->CreateItem(appId, k_EWorkshopFileTypeCommunity);
106// in callback (PublishedFileId_t id):
107UGCUpdateHandle_t u = SteamUGC()->StartItemUpdate(appId, id);
108SteamUGC()->SetItemContent(u, "C:/mod/content");
109SteamUGC()->SetItemPreview(u, "C:/mod/preview.png");
110SteamUGC()->SubmitItemUpdate(u, "initial version");
111```
112
113For consuming mods:
1141. Call `GetSubscribedItems` to enumerate subscribed items.
1152. Call `GetItemInstallInfo` to get the local folder path.
1163. Load mod content from disk.
1174. **Never assume a mod folder exists**; users unsubscribe mid-session. Always check validity before loading.
118
119### 6. Lobbies & Networking
120
121- **Matchmaking lobbies** (`ISteamMatchmaking::CreateLobby`, `RequestLobbyList`, lobby data key/values) for grouping players and exchanging connection metadata.
122- For transport, prefer **Steam Game Networking Sockets** (`ISteamNetworkingSockets`) over the legacy P2P API — it provides NAT punch-through, relays (SDR), and encryption.
123- Lobby data is the meeting point; actual gameplay traffic goes over the sockets connection negotiated via the lobby.
124
125**Hard rule**: do not use the legacy P2P API for new projects. Use Game Networking Sockets for NAT traversal + encryption.
126
127### 7. DLC & Ownership
128
129- Gate DLC content with `SteamApps()->IsDlcInstalled(dlcAppId)`.
130- Centralize the App ID; mismatches break ownership and stats queries.
131
132### 8. SteamPipe Builds
133
134| Concept | Meaning |
135|---|---|
136| **Depot** | A bucket of files (e.g. per-OS, per-DLC) |
137| **App build** | A set of depot snapshots published to a branch |
138| **Branch** | `default` (live) or beta branches (password-gated) |
139
140Upload via `steamcmd` + a `app_build_*.vdf` script (or the Web/Steamworks UI).
141
142**Hard rule**: always publish to a **beta branch first**, validate, then promote to `default`.
143
144Example PowerShell command to upload a build:
145
146```powershell
147steamcmd +login <build_account> +run_app_build /path/to/app_build_480.vdf +quit
148```
149
150Replace `<build_account>` with your Steamworks build account (never hardcode live credentials in scripts).
151
152## Pitfalls
153
1541. **Shipping `steam_appid.txt`**: lets the game run without ownership checks — remove it from release builds.
1552. **Never calling `RunCallbacks()`**: every async result (stats, leaderboards, UGC) silently never returns.
1563. **`StoreStats()` per event**: rate-limited and slow. Batch `SetStat`/`SetAchievement`, then `StoreStats()` once.
1574. **Using achievements that aren't defined on the partner site**: API calls no-op silently.
1585. **Blind Cloud overwrite**: loses progress on multi-PC users. Implement conflict handling.
1596. **Assuming Steam is always present**: `SteamAPI_Init` can fail (offline, no client). Degrade gracefully, never hard-crash.
1607. **Legacy P2P for new projects**: use Game Networking Sockets for NAT traversal + encryption.
1618. **Hardcoding the App ID in many places**: centralize it; mismatches break ownership/stats.
162
163## Verification
164
165- [ ] `SteamAPI_Init` failure is handled (game still launches, features disabled).
166- [ ] `SteamAPI_RunCallbacks()` runs every frame; `SteamAPI_Shutdown()` on exit.
167- [ ] `steam_appid.txt` is excluded from shipping builds.
168- [ ] Achievements/stats exist on the partner site; stats batched then `StoreStats()`.
169- [ ] Leaderboard upload uses the correct method (`KeepBest` vs `ForceUpdate`).
170- [ ] Cloud sync handles the multi-machine conflict case.
171- [ ] Workshop consumption tolerates missing/unsubscribed items.
172- [ ] Builds publish to a beta branch and are validated before promotion to `default`.
173
174## Related Skills
175
176- `game-mobile-store-integration` — the mobile-store equivalent (IAP, store services).
177- `game-console-porting-certification` — console online/cert when porting beyond PC.
178- `game-godot-multiplayer-networking` / `game-unreal-engine` — engine-side networking these lobbies feed into.
179
180## References
181
182- **Steamworks SDK documentation**: Stats & Achievements, Leaderboards, Remote Storage, UGC, Matchmaking, Game Networking Sockets, SteamPipe — available on the Steamworks partner site.
183- Load `references/` files when deeper API surface detail is needed (e.g. callback structures, VDF schema examples). Check the `references/` directory for supplementary docs before writing complex integration code.