I have over 800 games in my Steam library, and if you're anything like me, you've stared at a half-finished achievement list and thought: "there has to be a better way to manage this." The built-in Steam achievement UI is functional but bare-bones. No global rarity stats at a glance, no way to browse achievements across your entire library, and no keyboard-driven workflow. So I built SteamForge.
SteamForge is a cross-platform desktop app for viewing, unlocking, and managing Steam achievements. It lets you browse your full library with grid and list views, see global rarity percentages for every achievement, and navigate everything with keyboard shortcuts. It's built for completionists who want total control.
Why This Stack
Choosing the right tools was critical. I needed a desktop app that felt native, talked directly to Steam's C API, and had a modern, reactive UI. Here's where I landed:
Go for the Backend
Go was the natural choice for the backend layer. It provides excellent concurrency primitives via goroutines and channels, which turned out to be essential when fetching achievement data for hundreds of games concurrently. But the real deciding factor was cgo: Go's ability to call C functions directly meant I could interface with the Steamworks SDK without writing a separate native layer or shelling out to external processes.
The Steamworks SDK is a C/C++ library, and wrapping it with cgo keeps the integration tight. I wrote a thin C shim layer that exposes the specific Steamworks functions SteamForge needs, and Go calls into that layer directly. This gives me type safety on the Go side while still having full access to the native SDK.
Svelte 5 for the Frontend
Svelte 5 with its new runes system was a game-changer for state management. In a desktop app like SteamForge, there's a lot of state flying around: which game is selected, which achievements are loaded, filter/sort preferences, global rarity data, loading states per game, and more.
Svelte 5 runes ($state, $derived, $effect) let me express reactive state without the ceremony of stores or signals libraries. State flows naturally through the component tree, and derived computations (like filtered achievement lists or rarity-sorted views) update automatically. Coming from React, the lack of useEffect footguns and stale closure bugs was a breath of fresh air.
Wails v2 for Desktop
Wails v2 bridges Go and the web frontend into a single native binary. Unlike Electron, it uses the OS's native webview (WebKit on macOS, WebView2 on Windows, WebKitGTK on Linux), so the final binary is around 10 MB instead of 150+ MB. Wails also provides seamless Go-to-JS binding: you define Go methods on a struct, and Wails generates TypeScript bindings that the frontend can call as if they were local async functions.
This means the frontend calls GetAchievements(appId) and gets back a typed response, no HTTP server, no WebSocket layer, no serialization boilerplate. It just works.
Key Challenges
Steamworks SDK Inconsistencies
The Steamworks SDK is powerful but inconsistent. Some API calls return data synchronously, others use asynchronous callbacks, and a few require polling. Achievement icons, for example, are fetched through a callback-based system where you request the icon, get a handle back, then need to poll until the image data is ready.
I ended up building an abstraction layer that normalizes all of these patterns into Go channels. Every SDK call returns a channel, and the caller can select on it with a timeout. This made the Go code clean and predictable even when the underlying SDK behavior was anything but.
Another headache: some games report achievement data differently. A small number of games have achievements with empty names, duplicate API names, or broken icon URLs. SteamForge has a normalization layer that handles these edge cases and falls back to sensible defaults.
Caching Global Rarity Stats
Global achievement rarity percentages (e.g., "only 4.2% of players have this achievement") are fetched from Steam's servers. Making these calls for every game on every launch would be painfully slow and would hammer Steam's API rate limits.
I implemented a local SQLite cache with time-based expiry. When you open a game's achievements, SteamForge checks the cache first. If the data is fresh (less than 24 hours old), it uses the cached version. Otherwise, it fetches fresh stats in the background, updates the cache, and reactively updates the UI once the new data arrives. The user sees cached data instantly and gets fresh data moments later without any loading spinner.
The cache stores data per-game with a last-fetched timestamp, and there's a background goroutine that pre-fetches rarity data for recently played games during idle time. This makes the experience feel instant even for large libraries.
What I Learned
- cgo is powerful but demanding. Build times increase significantly, cross-compilation becomes harder, and debugging segfaults in C code from Go is not fun. But for native SDK integration, there's no real alternative.
- Svelte 5 runes are the real deal. The mental model is simpler than React hooks, and the compiler does the heavy lifting. I hit zero "why isn't this updating?" bugs during the entire project.
- Wails is underrated. The developer experience is excellent, and the resulting binary feels native. The hot-reload dev mode is fast, and the Go-to-JS bindings eliminate an entire category of boilerplate.
- Cache early, cache aggressively. In a desktop app that talks to external APIs, perceived performance is everything. Users don't care about data being 12 hours stale if the UI loads in 200ms.
What's Next
SteamForge is functional and I use it daily, but there's more I want to build:
- Achievement guides integration — pulling in community-written guides for hard-to-get achievements, displayed inline.
- Statistics tracking — tracking your completion rate over time with charts and milestones.
- Multi-account support — managing achievements across multiple Steam accounts from a single interface.
- Plugin system — letting the community build extensions for custom features like achievement notifications or Discord integration.
If you're a Steam completionist, give SteamForge a try. It's open source and I'd love feedback.