Skip to content
Joël Deffner edited this page Sep 4, 2026 · 2 revisions

System

Since v0.5.0. steam.system is the curated layer over ISteamUtils: the facts about the machine and the Steam client this app runs under, plus the two gamepad keyboards. It is called system because the generated ISteamUtils class already owns steam.utils.

Everything here reads the running Steam client, so it answers right away. showGamepadTextInput is the one exception: it opens the Big Picture keyboard and resolves when the user is done with it.

Some methods come from a different interface. gameLanguage and everything under the running app are ISteamApps reads, because they answer questions about this process rather than about its DLC, which is what DLC covers.

The System instance is created lazily and cached on the Steam object. The overlay calls of ISteamUtils are on Overlay, and avatars have their own decode in social.avatar; image here is the generic form for any Steam image handle.

appId

appId(): number

The app id this process is running under, as Steam sees it. steam.appId is the one init was given; this is Steam's answer, which is the better one to trust when the app id came from steam_appid.txt.

isSteamDeck

isSteamDeck(): boolean

Whether the app runs on a Steam Deck. Use it to switch to the controller-first UI and the larger text Valve's Deck Verified checks look for.

SDK 1.65 replaced the old IsSteamRunningOnSteamDeck with IsRunningOnSteamHardware, which names the hardware instead of answering yes or no, so this compares against k_ESteamHardwareTypeSteamDeck. It is false on every other machine, Steam Machine and Steam Frame included. Read steam.utils.IsRunningOnSteamHardware() directly when you care which one.

Client mode

isBigPicture(): boolean
isVr(): boolean
isChinaLauncher(): boolean

isBigPicture is Big Picture mode, which is also the mode a Steam Deck runs in, and it is what gates the gamepad keyboards. isVr is whether Steam runs in VR. isChinaLauncher is the Steam China build, which has its own rules for anti-addiction and age checks, so an app that ships there must branch on it.

uiLanguage

uiLanguage(): string

The language the Steam client UI is in, as an API language code, for example english or german.

gameLanguage

gameLanguage(): string

The language this app was launched in, same code format. This is the one to localize the game with: the user may run the client in one language and the game in another.

The running app

Since v0.6.0. The rest of this section is what ISteamApps says about the process you are in: where it lives on disk, which build and branch it is, how it was launched, and whose licence it runs under. All of it is a local read.

installDir

installDir(appId?: number): string | null

The absolute install directory of an app, defaulting to the running one, or null when Steam knows no path for that app id.

Steam also answers for an app the user owns but has not installed, with the path it would install to, so pair this with isAppInstalled before touching the files. The path is read into a 1024-byte buffer.

import { init } from 'steamwand.js';

const steam = init({ appId: 480 });
console.log(steam.system.installDir(), steam.system.buildId());
steam.close();

isAppInstalled

isAppInstalled(appId: number): boolean

Whether an app's files are on disk. Meant for the other app in a launcher or a bundle; DLC state is dlc.isInstalled.

buildId

buildId(): number

The build id of the running app, or 0 when the app is not running under Steam. Steamworks assigns a new one on every upload, so this is the version number to put in a bug report or a crash log.

currentBeta

currentBeta(): string | null

The beta branch the app is running from, or null on the default branch, which is where a user without a beta opt-in is. null is the normal answer for a shipped game.

listBetas

listBetas(): BetaBranch[]

The beta branches of the running app that this user may see, in Steam's order. Empty when the app has no branches.

Steam reports one flags field per branch; this spreads it into the five booleans of BetaBranch. Names are read into a 64-byte buffer and descriptions into a 256-byte one, and a branch Steam refuses at its index is skipped rather than pushed half-filled.

for (const beta of steam.system.listBetas()) {
  console.log(beta.name, beta.buildId, beta.installed ? '(installed)' : '');
}

setActiveBeta

setActiveBeta(name: string): void

Switches the app to a beta branch. The empty string switches back to the default branch.

Steam only records the choice. The branch is downloaded and the app runs from it after the next restart through Steam, so nothing changes in the running process. A private branch has to be unlocked with its password in the Steam client first.

Throws Error: steamwand: SetActiveBeta returned false (...) for an unknown or locked branch name.

launchCommandLine and launchQueryParam

launchCommandLine(): string
launchQueryParam(key: string): string

launchCommandLine is the arguments Steam launched this app with, or an empty string when there were none. This is where a steam://run/<appid>//<params> link ends up, and where a +connect string lands when a friend's invite started the app rather than finding it running. It is read into a 1024-byte buffer.

launchQueryParam is one parameter of a steam://run/<appid>//?key=value link, already parsed, or an empty string when the app was not launched with it. Pass the key with no leading ? or &.

onLaunchParameters

onLaunchParameters(listener: () => void): () => void

Subscribes to new launch arguments arriving for the running app, and returns an unsubscribe function.

It fires when Steam passes a steam://run/<appid>//<params> link to an app that is already up, which is how a second click on a store or web link reaches it. The callback carries no payload: read the new arguments with launchCommandLine or launchQueryParam.

const off = steam.system.onLaunchParameters(() => {
  console.log(steam.system.launchCommandLine());
});
// later: off();

appOwner and isFamilyShared

appOwner(): bigint
isFamilyShared(): boolean

appOwner is the Steam id of the account whose licence this app runs under, which is the local user except under Family Sharing. isFamilyShared is the same fact as a boolean.

A borrowed session ends the moment the owner starts playing, so a game that cares about long sessions should warn the player rather than lose their progress.

timedTrial

timedTrial(): { secondsAllowed: number; secondsPlayed: number } | null

The trial budget for an app the user is only trying out, or null when this is not a timed trial, which is the normal answer.

const trial = steam.system.timedTrial();
if (trial) console.log(trial.secondsAllowed - trial.secondsPlayed, 'seconds left');

ipCountry

ipCountry(): string

The two letter ISO 3166-1 country code Steam geolocated this user's IP to, for example DE. It is a guess from the IP address, not the country on the account, so treat it as a default for matchmaking or currency rather than a fact.

serverTime

serverTime(): Date

Steam's own wall clock time, as a Date. Use it wherever a local clock could be wrong or tampered with, for example a daily reward or an event window.

import { init } from 'steamwand.js';

const steam = init({ appId: 480 });
console.log(steam.system.serverTime().toISOString());
console.log(steam.system.gameLanguage(), steam.system.ipCountry());
steam.close();

Idle times

secondsSinceAppActive(): number
secondsSinceComputerActive(): number

Seconds since this app started, and seconds since the last user input anywhere on this computer. The second one is how you tell an idle machine from an idle game.

batteryPower

batteryPower(): number | 'ac'

Remaining battery charge in percent on a laptop or a handheld, or the string 'ac' when the machine runs on mains power. Steam reports 255 for mains power; this turns that into 'ac' so a plugged-in machine can never look like a full battery.

const power = steam.system.batteryPower();
if (power !== 'ac' && power < 20) console.log('save the game');

isOverlayEnabled

isOverlayEnabled(): boolean

Whether the Steam overlay can render over this app. The same read as overlay.isEnabled, here because it is a fact about the client.

image

image(handle: number): SteamImage | null

One Steam image by handle, as raw RGBA pixels. Handles come from other calls, for example the avatar handles on ISteamFriends. Two flat calls under the hood: GetImageSize for the dimensions, then GetImageRGBA into a buffer of exactly that size.

Returns null when Steam does not know the handle, which includes handle 0. Throws Error: steamwand: GetImageRGBA returned false (...) when the size was known but the pixels could not be read.

showGamepadTextInput

showGamepadTextInput(options: GamepadTextInputOptions): Promise<string | null>

Opens the full screen gamepad keyboard and resolves with what the user typed, or null if they dismissed it without submitting.

It only shows up in Big Picture mode and on Steam Deck. Everywhere else the show call fails and this rejects at once. The text is fetched with GetEnteredGamepadTextInput inside the dismissal callback, the only moment Steam still holds it.

const name = await steam.system.showGamepadTextInput({
  description: 'Your name',
  maxChars: 32,
});
if (name !== null) console.log(name);

Throws Error: steamwand: ShowGamepadTextInput returned false (not in Big Picture mode?).

The floating keyboard

showFloatingGamepadTextInput(
  mode: number, x: number, y: number, width: number, height: number,
): boolean
dismissFloatingGamepadTextInput(): boolean

showFloatingGamepadTextInput opens the floating keyboard over a text field, given that field's rectangle in pixels from the top left of the window. mode is an EFloatingGamepadTextInputMode: 0 single line, 1 multiple lines, 2 email, 3 numeric. It returns false outside Big Picture mode, and dismissFloatingGamepadTextInput returns true when a keyboard was open and is now closing.

Unlike showGamepadTextInput this pair does not return the text. Steam types into whatever field the app has focused, so the app reads its own field.

onIpCountryChanged

onIpCountryChanged(listener: () => void): () => void

Subscribes to the country changing under this user's IP, and returns an unsubscribe function. The callback carries no payload, so read the new value with ipCountry.

onLowBattery

onLowBattery(listener: (minutesLeft: number) => void): () => void

Subscribes to Steam's low battery warning. Steam sends it once the battery drops under 10 percent, and again every minute after that, with the minutes it thinks are left. A good moment to autosave.

const off = steam.system.onLowBattery((minutes) => {
  console.log(`${minutes} minutes left, saving`);
});
// later: off();

Types

SteamImage

Returned by image.

Field Type Meaning
width number Width in pixels.
height number Height in pixels.
rgba Buffer Raw pixels, 4 bytes each in RGBA order, width * height * 4 long.

BetaBranch

Since v0.6.0. One entry from listBetas. The five booleans are Steam's EBetaBranchFlags bits, spread out.

Field Type Meaning
name string Branch name, the one setActiveBeta takes.
description string Description the developer wrote, empty if there is none.
buildId number Build id currently on that branch.
lastUpdated number When the branch was last updated, as a Unix time in seconds.
isDefault boolean The app's default branch, the one a user without an opt-in runs.
available boolean The branch is available to this user.
isPrivate boolean The branch needs a password to opt into.
selected boolean The branch the user picked in the Steam client.
installed boolean The branch that is installed right now.

GamepadTextInputOptions

Taken by showGamepadTextInput. Only description is required.

Field Type Default Meaning
description string none Prompt shown above the keyboard.
mode number 0 EGamepadTextInputMode: 0 normal, 1 password (the text is masked).
lineMode number 0 EGamepadTextInputLineMode: 0 single line, 1 multiple lines.
maxChars number 256 Maximum characters the user may enter.
existingText string '' Text the field starts with.

Errors

Shape When
Error: steamwand: ShowGamepadTextInput returned false (not in Big Picture mode?) The keyboard could not be shown, which is the normal outcome outside Big Picture mode and Steam Deck.
Error: steamwand: GetEnteredGamepadTextInput returned false (...) Steam had text but refused to copy it out.
Error: steamwand: GetImageRGBA returned false (...) image read a size but not the pixels.
Error: steamwand: SetActiveBeta returned false (...) setActiveBeta got an unknown branch name, or one the user has not unlocked.

Nothing here throws SteamResultError: no method waits for a call result.

What this layer does not do

The ISteamApps calls this layer does not take are on DLC or on the raw steam.apps; that page lists them. The rest of ISteamUtils is on the raw generated steam.utils:

  • The overlay calls, wrapped separately on Overlay: SetOverlayNotificationPosition, SetOverlayNotificationInset, BOverlayNeedsPresent.
  • The call-result plumbing, which the dispatch pump owns: IsAPICallCompleted, GetAPICallFailureReason, GetAPICallResult.
  • IsRunningOnSteamHardware in its raw form, when you need to tell a Steam Machine or a Steam Frame from a Deck, plus GetSteamHardwareDefaultConfig.
  • IsRunningUnderProton, GetIPv6ConnectivityState, GetConnectedUniverse, GetIPCCallCount and CheckFileSignature.
  • StartVRDashboard, IsVRHeadsetStreamingEnabled, SetVRHeadsetStreamingEnabled and SetGameLauncherMode.
  • InitFilterText and FilterText, Steam's profanity filter.
  • DismissGamepadTextInput, which closes the full screen keyboard from code instead of waiting for the user.

Flat API explains the calling convention.

Next: Controllers, the other layer a Steam Deck build needs.

Clone this wiki locally