Random Steam Game Picker is an ultra-fast, sub-second web utility designed to solve backlog analysis paralysis. Enter any 17-digit Steam ID, vanity name, or Steam Community URL to instantly roll a random game from your library.
Need your data offline? Use the built-in Steam Library CSV Exporter to download your entire public collection—complete with game titles, App IDs, playtime stats, last-played timestamps, and real-time Steam Deck compatibility tracking—straight to an Excel-ready spreadsheet.
Built for gamers with massive backlogs, limited decision-making energy, and a suspicious tendency to press Choose Again.
- Instant Sub-Second Game Roulette: Leverages a high-performance, multi-tier hybrid cache to deliver near-zero latency rolls that beat typical 8-second scrapers.
- Smart Backlog Filtering: Toggle "Unplayed-Only" mode to instantly isolate and roll games with zero recorded Steam playtime.
- Bulk Steam to CSV Export: Download your entire game index at
/library-exportwith granular columns tracking hidden metadata. - Steam Deck Compatibility Audit: Automates batch-processing queries against the Steam Store API to index
verified,playable,unsupported, orunknowndeck statuses. - Zero Authentication Required: Operates entirely over public Steam APIs—no Steam sign-in or account credentials required.
- Browser-Based Exclusions: Block specific games or persistent titles from your roulette pool right in your local session.
- Accepts a 17-digit Steam ID, vanity name, or Steam Community vanity URL
- Selects a random game the user owns from their public Steam library
- Filters the pool to games with no Steam playtime recorded when unplayed-only mode is enabled
- Supports repeated picks without returning to the home page
- Caches Steam library and Store API data to reduce upstream requests
- Blocks unwanted games from future picks in the current browser and can reset the blocked list
- Refreshes cached library data after the user's Steam library changes
- Displays the selected game's description and recorded playtime
- Opens the selected game through the Steam desktop client
- Exports a public Steam library from the
/library-exportpage - Includes game name, App ID, playtime, last-played time, and Steam Deck compatibility in CSV exports
- Accepts numeric Steam IDs, vanity names, and Steam Community vanity URLs for both picking and exporting
Steam must be able to access the profile's game details. The saved Steam identity, unplayed-only preference, blocked games, and library-refresh timing are stored in browser cookies, so they do not synchronize across browsers or devices. The application does not require Steam sign-in.
- Enter a Steam ID or vanity URL.
- The application loads the user's public Steam library.
- It removes games that do not match the selected options or that were blocked in the current browser.
- It randomly selects one eligible game and loads its details.
- Play it, choose again, or block it and roll once more.
-
Open
/library-exportand enter a Steam ID, vanity name, or Steam Community vanity URL. -
Vanity input is resolved to a numeric Steam ID through the Steam Web API.
-
The application loads the public library and fetches Steam Deck compatibility in batches.
-
The browser downloads
steam-library-{steamId}.csvwith these columns:Column Value gameSteam game name idSteam App ID hoursTotal recorded playtime in decimal hours hours_2_weeksRecent playtime in decimal hours; 0when Steam reports nonehours_windowsWindows playtime in decimal hours hours_macmacOS playtime in decimal hours hours_linuxLinux playtime in decimal hours last_playedUTC timestamp in yyyy-MM-ddTHH:mm:ssZformat, or blank when Steam reports no meaningful datesteam_deckverified,playable,unsupported, orunknownsteam_store_urlSteam Store URL derived from App ID: https://store.steampowered.com/app/{appId}/
All playtime columns use invariant decimal hours with up to two decimal places, including
0 for zero minutes. These additional fields use the owned-library response and require
no additional Steam requests. Store metadata such as developer, genres, and pricing is
deferred until a bounded, batched, cached design is available.
If Deck compatibility cannot be retrieved for a game, the export uses unknown rather than failing the whole download. Exports are limited to one request per IP address every 72 hours to protect Steam API capacity.
- .NET 10 and ASP.NET Core
- Blazor Web App with Interactive Auto components
- Steam Web API and Steam Store API
- ASP.NET Core HybridCache with memory and SQLite-backed caching
- SQLite for lightweight application data
- Server-rendered Razor components with Interactive Auto picker and export forms
- ASP.NET Core rate limiting, Data Protection, and liveness/readiness health checks
- Optional Mission Control telemetry
| Project | Responsibility |
|---|---|
RandomSteamGame |
ASP.NET Core host, routed pages, controllers, middleware, persistence, and server services |
RandomSteamGame.Client |
Interactive picker components, browser state, and the browser-facing API client |
RandomSteamGame.Shared |
Contracts and abstractions shared by the host and client |
SteamApiClient |
Steam Web API and Store API clients plus caching |
RandomSteamGame.Tests |
Host, controller, provider, persistence, and component-supporting tests |
SteamApiClient.Tests |
Tests for the Steam integration library |
The central picker flow is GameController → GameProviderFactory → SteamProvider → SteamApiClient. The /library-export page is server-rendered for metadata and indexability, while LibraryExportForm uses Interactive Auto for browser identity access and vanity resolution.
Clone the repository:
git clone https://github.com/JoyfulReaper/RandomSteamGame.git
cd RandomSteamGameStore the Steam API key with .NET user secrets. Do not put a real key in a tracked configuration file:
dotnet user-secrets set "Steam:ApiKey" "YOUR_STEAM_WEB_API_KEY" --project RandomSteamGame/RandomSteamGame.csprojRestore, build, and run the application:
dotnet restore RandomSteamGame.slnx
dotnet build RandomSteamGame.slnx --no-restore
dotnet run --project RandomSteamGame/RandomSteamGame.csproj --no-buildThe development profile listens at http://localhost:5182.
SQLite data and development Data Protection keys are created automatically. Mission Control is disabled by default and is not required for local development.
The only secret required for normal local use is the Steam Web API key:
| Configuration key | Environment variable | Purpose |
|---|---|---|
Steam:ApiKey |
Steam__ApiKey |
Reads public library data from the Steam Web API |
The tracked RandomSteamGame/appsettings.json also contains non-secret settings for caching, rate limiting, allowed origins, canonical hosts, Data Protection, and optional Mission Control telemetry.
Steam options are validated at startup. Steam:ConnectionString must be nonblank; validation does not open the cache database. All six Steam:Cache policies must be explicitly configured with AbsoluteMinutes from 1 through 525600 (one year). Steam:RateLimiting must specify PermitLimit from 1 through 1000000 and WindowSeconds from 1 through 86400 (one day). These generous bounds reject obvious mistakes while accommodating the shipped 43200-minute caches and future rate-limit tuning. Invalid settings are rejected with their configuration paths, without clamping or substituting defaults.
Important operational settings include:
| Configuration key | Purpose |
|---|---|
Application:NetworkMode |
Public (default) or AltNet; voting is Public-only; also controls defaults for remote Steam browser assets and beta probing |
Application:NetworkName |
Optional display name such as I2P, Yggdrasil, or DN42; does not select capabilities |
Application:AllowRemoteBrowserAssets |
Optional override; null allows Steam browser assets in Public and disables them in AltNet |
Application:EnableBetaProbe |
Optional override; null enables beta probing in Public and disables it in AltNet; public beta links are shown only in Public |
Application:CanonicalOrigin |
Externally visible origin for canonical/social metadata and sitemap URLs; unset Public uses the existing production HTTPS origin; required in AltNet, where HTTP or HTTPS is accepted |
Application:BetaHost |
Host that receives X-Robots-Tag: noindex, nofollow |
DataProtection:KeysPath |
Optional persistent Data Protection key-ring location |
Steam:ConnectionString |
SQLite connection string for Steam response caching |
Steam:Cache:* |
Cache durations for libraries, app details, vanity results, and Deck compatibility |
Steam:RateLimiting:* |
General Steam API request limit; CSV export has a separate policy |
Steam:LibraryExport:RateLimitMode |
PerIp (default) or Global; independent of network mode |
Steam:LibraryExport:GlobalConcurrency |
Maximum active exports across all IPs in PerIp mode (default 2); Global mode always allows only one |
Steam:LibraryExport:GlobalCooldownMinutes |
Global-mode cooldown from acceptance, including failed attempts (default 20; range 1–43200 minutes) |
Ingress:EnableForwardedHeaders |
Optional override; null trusts configured proxy addresses in Public and ignores forwarding in AltNet |
Ingress:EnableCloudflareVisitorHeader |
Optional override; null enables trusted Cloudflare scheme metadata in Public and disables it in AltNet; requires forwarding enabled |
Ingress:TrustedProxies |
Exact immediate-peer IP addresses allowed to supply forwarding headers; defaults to 127.0.0.1 and ::1 |
Cors:AllowedOrigins |
Browser origins permitted to call the API |
MissionControl:* |
Optional deployment and game-pick telemetry configuration |
Telemetry:VisitorHashKey |
Optional key used to pseudonymize visitor identifiers for telemetry |
When DataProtection:KeysPath is empty, development and non-Windows deployments use .keys/data-protection beneath the application content root. Production Windows deployments use a machine-level application-data directory.
For example, set Application__NetworkMode=AltNet, Application__NetworkName=I2P,
and Application__CanonicalOrigin=http://example.b32.i2p
to omit Steam header artwork from game responses, restrict Steam descriptions to
text, formatting, and clickable links, and disable the beta probe/banner. Changing
the display name does not change these capabilities. The optional boolean overrides
can enable either capability independently for a deployment that supports it.
Restricted descriptions omit all embedded images and inline styles; clickable links
can still lead to external sites. Steam API access remains server-side.
The origin must contain only an HTTP(S) scheme, host, and optional port, without
credentials, a path (other than /), query, or fragment. Public requires HTTPS;
AltNet requires an explicit origin and accepts intentional HTTP origins. Incoming
Host headers do not select the canonical origin. The same origin is used for
canonical links, OpenGraph, JSON-LD application URLs, sitemap URLs, and the AltNet
footer/404 example. Public keeps its existing network and beta links; AltNet shows
the current network instead and omits public beta links even if probing is enabled.
/robots.txt and /sitemap.xml are generated from configuration. Public allows
crawling and advertises its configured sitemap with the existing four page URLs.
AltNet uses Disallow: / without advertising a sitemap; its sitemap remains
available using the configured origin. Robots directives are advisory, not access
control.
Public production keeps Secure antiforgery and server-written Steam identity
cookies; Public development keeps its existing request-dependent antiforgery
policy. AltNet derives both cookie security and the effective request scheme from
Application:CanonicalOrigin, so an intentional HTTP origin works behind an HTTP
tunnel without Secure cookies, while an HTTPS origin retains them even when the
backend listener uses HTTP. Antiforgery validation, HttpOnly on antiforgery cookies,
SameSite=Lax, host-only scope, and cookie lifetimes remain unchanged. Browser-written
Steam identity, exclusions, and cache-reset cookies already use the browser's actual
protocol. Visitor telemetry uses a keyed IP hash, not a visitor cookie.
AltNet ignores X-Forwarded-For, X-Forwarded-Proto, and CF-Visitor by default,
including from loopback. Public keeps trusted loopback proxy/Cloudflare support.
To enable forwarding on an ingress that supplies reliable client addresses, set
Ingress__EnableForwardedHeaders=true and configure Ingress:TrustedProxies with
its exact IP addresses. That ingress must strip visitor-supplied forwarding headers
and write its own values; address trust alone cannot distinguish headers passed
through by a tunnel. Cloudflare metadata has a separate opt-in for AltNet. Only one
forwarding hop is consumed; untrusted peers cannot change the effective client IP.
AltNet's effective scheme always comes from its configured origin.
For an ingress that presents every visitor as the same local address, configure:
Steam__LibraryExport__RateLimitMode=Global
Steam__LibraryExport__GlobalCooldownMinutes=20Global mode allows one active export request, rejects additional requests with HTTP 429, and atomically reserves the shared cooldown after provider/Steam ID validation and before Steam work starts. Completion does not restart the cooldown; failures consume it. Rejections return readable error text and Retry-After seconds (rounded up). Capacity rejections before the cooldown reservation or after its expiry suggest a five-second retry; availability still depends on completion. State is local to this process and resets on restart. PerIp mode preserves the 72-hour cooldown after a successful export, one active request per IP, and the configurable global capacity. Export policy hints follow the selected policy.
JavaScript cookie tests can be run with
node --test RandomSteamGame.Tests/cookieHelper.test.mjs in addition to the .NET suite.
/support includes optional Ko-fi/PayPal donations, curated feature voting, GitHub
suggestions, private sponsorship inquiries, and Kyle's portfolio. Donations and
sponsorship agreements are separate; sponsors must contact Kyle before paying.
Existing picker support links are preserved.
The InteractiveAuto voting board makes all API calls from the browser through
same-origin fetch and the existing dynamic antiforgery token flow. The backend uses
GET /api/features and POST /api/features/{id}/vote with a required supported
boolean, a protected HttpOnly cookie, and separate rate/capacity limits. This is
anonymous browser-based support, not verified one-person-one-vote.
Startup never seeds proposals. Kyle can manually run the idempotent initial proposal SQL against the initialized application database. See proposal maintenance for the command, rerun behavior, and adding/editing accepted proposals. No production database is modified automatically.
Proposals support open, planned, completed, and closed. Only open accepts
vote mutations; all statuses retain visible totals/selections. The new voting
tables use additive, idempotent CREATE TABLE IF NOT EXISTS initialization.
The API is enabled only with Application:NetworkMode=Public. Every AltNet
deployment (DN42, I2P, Yggdrasil) returns 404 before issuing voter cookies or invoking
voting persistence. The mode is process-wide: alternate ingress paths routed to
one Public instance would still reach its API. Use separate configured instances
for network isolation; this change does not modify proxies or production routing.
See anonymous feature voting for the schema,
API contract, cookie lifecycle, FeatureVoting:* settings, security model, and
Public-only availability and deployment considerations. Persist the existing Data Protection
keys and database. IP abuse signals stay in server memory and are not stored in
votes. No additional infrastructure or secret is required.
The repository Dockerfile builds on the .NET 10 SDK image, runs the full Release test suite as an image-build gate, publishes the application, and runs it as the image's non-root application user.
The Dockerfile declares port 5182. Set ASPNETCORE_HTTP_PORTS=5182 in the container environment, or publish whichever ASP.NET Core port your deployment configures. Mount persistent storage for the SQLite files and Data Protection key ring when deploying containers that may be replaced or recreated. To use the Dockerfile's prepared key directory, set DataProtection__KeysPath=/data-protection and mount a volume there. The application provides:
/health/livefor process liveness/health/readyfor local dependency readiness, including SQLite and the Data Protection key directory
The production deployment is designed to run behind a loopback reverse proxy or Cloudflare Tunnel. Forwarded client IP and scheme headers are accepted only from loopback proxies; update the trusted-proxy configuration if a proxy reaches the application from another address, especially when relying on per-IP rate limiting.
The production Random Steam Game deployment runs on GreenCloud infrastructure.
Disclosure: This is an affiliate link. If you purchase through it, I may receive a commission at no additional cost to you.
The footer's hosting message is configured independently of the deployment's network mode
through Hosting:Message, Hosting:ProviderName, Hosting:Url, Hosting:AffiliateUrl, and
Hosting:ShowAffiliateDisclosure. The default keeps the GreenCloud message and disclosure.
An affiliate URL takes precedence over the regular URL. Leave both URLs empty to display
the provider as plain text. Disclosure appears only for an affiliate link when enabled.
For a message-only deployment, for example:
Hosting__Message=Available via I2P
Hosting__ProviderName=
Hosting__Url=
Hosting__AffiliateUrl=
Hosting__ShowAffiliateDisclosure=falseLeave both the message and provider name empty to hide the hosting block entirely.
The home page and library-export page publish canonical URLs, descriptions, and social metadata. The application also serves robots.txt, sitemap.xml, and WebApplication JSON-LD. The configured beta host sends X-Robots-Tag: noindex, nofollow, and generated random-game result pages are excluded from indexing.
Run all solution tests from the repository root:
dotnet test RandomSteamGame.slnxTo run an individual test project:
dotnet test RandomSteamGame.Tests/RandomSteamGame.Tests.csproj
dotnet test SteamApiClient.Tests/SteamApiClient.Tests.csprojFocused issues and pull requests are welcome. Describe the user-visible behavior, keep unrelated changes separate, and add or update tests when the behavior is testable.
Use GitHub Issues for bug reports and feature proposals.
Planned or considered improvements include:
- Steam sign-in and account-linked preferences
- Permanent blocked games that follow the user across devices
- Favorites and picker history
- More game filters
- Saved and shareable game lists
These are future ideas, not currently implemented features.
- Developer handoff — deeper architecture, request flow, caching, deployment, and debugging notes
- Technical privacy/data inventory — public engineering reference for collection, storage, retention/deletion behavior, and operational unknowns; see
/privacyfor the Privacy Policy - Premium feature issues — account-related roadmap and likely implementation areas
Random Steam Game Picker is licensed under the MIT License.
The license does not protect you from backlog guilt or from the picker selecting exactly the game you were secretly avoiding.

