Skip to content

Core API

Joël Deffner edited this page Sep 4, 2026 · 4 revisions

Core API

The handwritten surface: one function to start, one class to hold everything, four error types. The fifteen curated layers have their own pages (Workshop, Stats, Cloud, Leaderboards, Lobbies, Social, Overlay, Auth, System, Capture, Controllers, DLC, Inventory, P2P, Recording), and the generated interface classes are on Flat-API.

import { init, Steam, SteamInitError, SteamResultError, flat } from 'steamwand.js';

init

function init(opts?: InitOptions): Steam;

Initializes the Steam API and returns a ready Steam. In order:

  1. When opts.appId is given, sets process.env.SteamAppId and process.env.SteamGameId to that number as a string.
  2. Loads the redistributable (opts.libPath, or the bundled one for this platform).
  3. Calls SteamAPI_InitFlat with a 1024-byte error buffer.
  4. On failure, throws SteamInitError carrying Valve's own diagnostic text.
  5. Calls SteamAPI_ManualDispatch_Init, creates the dispatch pump on the pipe from SteamAPI_GetHSteamPipe, and starts it.

steam.appId is opts.appId when given, otherwise Number(process.env.SteamAppId ?? 0).

Throws. SteamInitError when SteamAPI_InitFlat returns anything other than k_ESteamAPIInitResult_OK. err.initResult is the raw ESteamAPIInitResult value, and err.message is Steam's text, or SteamAPI_InitFlat failed (<result>) when Steam wrote nothing. A missing or unloadable library throws from koffi before that point, and an unsupported process.platform throws steamwand: unsupported platform <name>.

One session at a time. Manual dispatch owns the process-wide callback queue, so a second session would pump the same queue twice and steal the first one's callbacks. init guards against that: while a session is open it throws

Error: steamwand: a Steam session is already open (one init per process); call close() on it first

before touching the library. This is a plain Error, not SteamInitError. Sequential sessions are fine: close releases the lock, and an init after it starts a fresh session. That is what lets a test suite or a workbench switch app ids without restarting the process.

InitOptions

Option Type Default Meaning
appId number none App id to initialize under. Omit to rely on steam_appid.txt or an existing SteamAppId environment variable.
libPath string the bundled runtime/<platform>/steam_api* file Absolute path of the steam_api library to load.
pumpIntervalMs number 50 Manual-dispatch pump interval in milliseconds.

isSteamRunning

function isSteamRunning(libPath?: string): boolean;

Reports whether a Steam client is running on this machine. Loads the library but does not start the Steam API, so it is safe to call on its own, before init, to tell "Steam is not running" apart from the other init failures. libPath overrides the bundled redistributable, as in InitOptions.

restartAppIfNecessary

function restartAppIfNecessary(appId: number, libPath?: string): boolean;

Wraps SteamAPI_RestartAppIfNecessary. Call it at the very top of the process, before init. When it returns true, Steam is relaunching your app under its own control, so exit at once and let that copy take over. It always returns false when a steam_appid.txt sits next to the executable, which is why that file belongs in development only.

class Steam

Returned by init. The constructor is public (new Steam(native, dispatch, appId)), but it expects an already initialized SteamNative and a started SteamDispatch, so use init.

Properties

Member Type Notes
appId number The app id this instance was initialized with. Readonly.
native SteamNative The loaded library and the symbol cache. Readonly.
dispatch SteamDispatch The running callback pump. Readonly.

Interface getters

Each getter builds its interface class on first access and caches it for the lifetime of the Steam object. Construction calls the versioned accessor (SteamAPI_SteamApps_v009 and friends) and throws steamwand: <accessor> returned null (is Steam initialized?) if Steam hands back nothing.

All 25 interfaces have one, named after the interface with the leading ISteam dropped and the first letter lowercased:

Getter Type
steam.user flat.ISteamUser
steam.friends flat.ISteamFriends
steam.utils flat.ISteamUtils
steam.matchmaking flat.ISteamMatchmaking
steam.matchmakingServers flat.ISteamMatchmakingServers
steam.parties flat.ISteamParties
steam.remoteStorage flat.ISteamRemoteStorage
steam.userStats flat.ISteamUserStats
steam.apps flat.ISteamApps
steam.networking flat.ISteamNetworking
steam.screenshots flat.ISteamScreenshots
steam.music flat.ISteamMusic
steam.http flat.ISteamHTTP
steam.input flat.ISteamInput
steam.controller flat.ISteamController
steam.ugc flat.ISteamUGC
steam.htmlSurface flat.ISteamHTMLSurface
steam.inventory flat.ISteamInventory
steam.timeline flat.ISteamTimeline
steam.video flat.ISteamVideo
steam.parentalSettings flat.ISteamParentalSettings
steam.remotePlay flat.ISteamRemotePlay
steam.networkingMessages flat.ISteamNetworkingMessages
steam.networkingSockets flat.ISteamNetworkingSockets
steam.networkingUtils flat.ISteamNetworkingUtils

Curated getters

Fifteen task-level layers and the async wrapper sit on top, cached the same way. Each is built on first access from the interface it wraps, this session's dispatch pump, steam.on and steam.once where it needs callbacks, and (for workshop) steam.appId.

Getter Type Wraps Page
steam.workshop Workshop steam.ugc Workshop
steam.stats Stats steam.userStats Stats
steam.cloud Cloud steam.remoteStorage Cloud
steam.leaderboards Leaderboards steam.userStats Leaderboards
steam.lobbies Lobbies steam.matchmaking Lobbies
steam.social Social steam.friends, steam.utils Social
steam.overlay Overlay steam.friends, steam.utils Overlay
steam.auth Auth steam.user Auth
steam.system System steam.utils, steam.apps System
steam.capture Capture steam.screenshots Capture
steam.controllers Controllers steam.input Controllers
steam.dlc Apps steam.apps DLC
steam.items Inventory steam.inventory Inventory
steam.p2p P2P steam.networking P2P
steam.recording Timeline steam.timeline Recording
steam.async flat.SteamAsync every interface Flat API

Nine of those getters are named after the job rather than after the interface, because the generated accessor already holds the obvious name: social and overlay because steam.friends is ISteamFriends, auth because steam.user is ISteamUser, system because steam.utils is ISteamUtils, capture because steam.screenshots is ISteamScreenshots, controllers because steam.input is ISteamInput, dlc because steam.apps is ISteamApps, items because steam.inventory is ISteamInventory, and recording because steam.timeline is ISteamTimeline. The classes behind steam.dlc, steam.items, and steam.recording are still called Apps, Inventory, and Timeline; only the getters are renamed.

steam.async is the generated layer, not a curated one: it returns every flat call that yields a SteamAPICall_t as a promise of the decoded result struct, grouped by interface, and a non-OK EResult inside that struct is not an error there.

const r = await steam.async.userStats.FindLeaderboard('Quickest Win');
console.log(r.m_bLeaderboardFound, r.m_hSteamLeaderboard);

steamId

steamId(): bigint;

The local user's 64-bit Steam id, straight from ISteamUser.GetSteamID().

accountId

accountId(): number;

The lower 32 bits of the Steam id, as a number (Number(this.steamId() & 0xffffffffn)). This is the value the UGC query functions want, for example steam.workshop.getUserItems(1, steam.accountId()).

on

on<T>(callbackName: string, listener: (data: T) => void): () => void;

Subscribes to a plain Steam callback by its struct name, for example 'ItemInstalled_t' or 'DownloadItemResult_t'. The name is looked up in the generated callback table, the payload is decoded with the layout for this platform (the win64 table on Windows, posix elsewhere), and the decoded object is handed to the listener. Fields keep their C names.

Returns an unsubscribe function. T is not checked against the layout, so declare it from the struct's real fields.

const stop = steam.on<{ m_unAppID: number; m_nPublishedFileId: bigint }>(
  'ItemInstalled_t',
  (d) => console.log(d.m_unAppID, d.m_nPublishedFileId),
);
stop();

Throws steamwand: unknown callback struct '<name>' for a name that is not in the table. Call results (the reply to an async call, such as CreateItemResult_t) do not arrive here; they resolve the promise returned by the call instead.

once

once<K>(callbackName: K, match?: (data) => boolean): Promise<SteamCallbackMap[K]>;

Awaits the first plain callback of callbackName that match accepts. Same lookup and same per-platform decode as on, but it settles a promise instead of calling a listener, and it unsubscribes itself once it does. match defaults to accepting the first callback.

This is the awaitable form for the flat calls that answer through a broadcast callback rather than a call result, which is a different mechanism: a call result belongs to one SteamAPICall_t handle, a broadcast callback belongs to nobody, so it has to be matched by hand. GetAuthSessionTicket is the canonical case, and match is how you tell your ticket's confirmation from another one that happens to be in flight.

const ticket = Buffer.alloc(1024);
const size = Buffer.alloc(4);
const handle = steam.user.GetAuthSessionTicket(ticket, ticket.length, size, null);
const r = await steam.once('GetAuthSessionTicketResponse_t', (e) => e.m_hAuthTicket === handle);
console.log(r.m_eResult);

The pump keeps the process alive while the promise is pending, so a script that does nothing but await one of these will not exit early.

Throws steamwand: unknown callback struct '<name>' for an unknown name. The promise rejects with steamwand: dispatch stopped while waiting for a callback when close runs while it is still waiting.

auth.getSessionTicket, auth.getWebApiTicket and system.showGamepadTextInput are built on this.

close

close(): void;

Stops the pump and calls SteamAPI_Shutdown. Idempotent: the second call returns immediately. Pending async calls reject with steamwand: dispatch stopped while call was in flight, and pending once promises with steamwand: dispatch stopped while waiting for a callback.

It also releases the one-session-per-process lock, so init may run again afterwards. Do not use this Steam, its interfaces, or its curated layers after close; take fresh ones from the new session.

Value conventions

  • 64-bit values are bigint. Steam ids, published file ids, UGC query handles, update handles, and API call handles. Write literals with the n suffix: steam.workshop.getItem(3141592653n). Parameters that accept an id are typed bigint | number so a small literal still works, but returns are always bigint.

  • Out parameters are Buffers you allocate. The generated signature is Buffer | null, and the size argument is separate, so pass buf.length yourself. Read the result back with the normal Buffer methods.

    const name = Buffer.alloc(256);
    if (steam.apps.GetCurrentBetaName(name, name.length)) {
      console.log(name.toString('utf8', 0, name.indexOf(0)));
    }
  • const char * returns are plain strings. koffi copies them, so there is nothing to free.

  • String array parameters use stringArray. Anywhere the flat API takes a SteamParamStringArray_t *, pass stringArray(['gameplay', 'ui']).

  • Enums are plain const objects, not TypeScript enums: flat.EResult.k_EResultOK, flat.EWorkshopFileType.k_EWorkshopFileTypeCommunity.

Errors

SteamInitError

class SteamInitError extends Error {
  readonly initResult: number; // ESteamAPIInitResult
}

Thrown only by init. message is Valve's diagnostic text from the SteamAPI_InitFlat error buffer, so it says things like which app id was expected or that no Steam client is running.

SteamResultError

class SteamResultError extends Error {
  readonly operation: string; // e.g. 'CreateItem'
  readonly result: number; // EResult
}

Thrown by every curated layer when a call completes with a non-OK EResult. The message is ${operation} failed: ${eResultName(result)}, for example SubmitItemUpdate failed: k_EResultAccessDenied or FileWriteAsync failed: k_EResultLimitExceeded. The generated layer never throws it: steam.async hands the struct back with whatever EResult is in it.

SteamApiCallError

class SteamApiCallError extends Error {
  readonly callbackId: number;
}

Thrown when the call itself never produced a usable result, which is different from a call that completed with a bad EResult. Four messages:

Message Cause
Steam returned an invalid API call handle The flat function returned handle 0. callbackId is 0.
expected callback id <n> for this call, Steam completed it with id <m> The caller passed an expected callback id and the completion carried a different struct. Since 0.3.0, see How It Works.
SteamAPI_ManualDispatch_GetAPICallResult failed Steam refused to hand over the completed result.
Steam reported an IO failure for this API call Steam set the bFailed flag on the result.

eResultName

function eResultName(result: number): string;

The EResult constant name for a number, for example 1 gives 'k_EResultOK'. Unknown values come back as `EResult(${result})`.

Plain Errors

Not every failure has a class. These are thrown as Error, all prefixed with steamwand:.

Message Thrown by
a Steam session is already open (one init per process); call close() on it first init, while another session is open.
unsupported platform <name> The loader, on a process.platform that is not win32, linux, or darwin.
<accessor> returned null (is Steam initialized?) An interface constructor.
unknown callback struct '<name>' steam.on and steam.once.
dispatch stopped while call was in flight close(), for each pending call.
dispatch stopped while waiting for a callback close(), for each pending steam.once.
buffer too small for struct (<n> < <size>) decodeStruct, on a payload shorter than the layout.
no generated layout for struct <name> flat.layoutOf, for a struct with no layout table (unions are excluded).

Package exports

Everything below is exported from the package root.

Export Kind Purpose
init function Initialize Steam and start the pump. Returns Steam.
Steam class The handle returned by init: interfaces, callbacks, shutdown.
isSteamRunning function Whether a Steam client is running. Safe before init.
restartAppIfNecessary function Relaunch through Steam when started from the executable. Call before init.
InitOptions type Options accepted by init.
SteamNative class The loaded steam_api library, its core exports, and the symbol cache.
SteamDispatch class The manual dispatch pump: runFrame, callResult, on, start, stop.
SteamApiCallError class An async call that produced no usable result.
SteamInitError class SteamAPI_InitFlat failed.
SteamResultError class A call completed with a non-OK EResult.
eResultName function EResult number to constant name.
decodeStruct function Decode raw callback bytes with a StructLayout.
StructLayout type { size, fields } for one struct on one platform.
FieldLayout type { name, offset, type } for one field.
FieldType type The field type union used by FieldLayout.
stringArray function Build a SteamParamStringArray_t argument from a string[].
out object Typed out-parameter buffers for the flat API: out.bool(), out.uint64(), and friends.
OutParam type What one out.*() call returns: a buffer plus a decoded value.
Workshop class The curated workshop layer. See Workshop.
QueryOptions type Language and description options for workshop queries.
UpdateProgress type Status and byte counts reported during an upload.
WorkshopItem type One workshop item, decoded and named.
AdditionalPreview type One extra preview on an item: url, file name, and EItemPreviewType.
WorkshopItemUpdate type The fields one submitUpdate writes.
WorkshopStatistic type Names of the item statistics that queries return.
UserItemsPage type One page of a user's items plus totalResults.
Stats class The curated achievements and stats layer. See Stats.
AchievementState type achieved plus the unlock time, or null while locked.
AchievementDisplay type Localized name, description, and the hidden flag.
Cloud class The curated Steam Cloud layer. See Cloud.
CloudFile type One entry from listFiles: name and size.
CloudFileInfo type Size and modification time of one cloud file.
CloudQuota type Total and still free cloud bytes for this app.
Leaderboards class The curated leaderboards layer. See Leaderboards.
LeaderboardInfo type Handle, name, entry count, sort method, display type.
ScoreUploadResult type Whether the score changed, and the ranks around it.
LeaderboardEntry type One downloaded row: user, rank, score, details, UGC handle.
DownloadOptions type Which entries to download and how many details to read.
Lobbies class The curated lobbies layer. See Lobbies.
LobbySearchOptions type Filters for one lobby search.
LobbyChatMessage type Sender and text of one lobby chat message.
Social class The curated friends, presence, and avatar layer. See Social.
Friend type One friend list entry: id, name, state, relationship.
Avatar type Decoded avatar: size plus RGBA pixels.
AvatarSize type 'small' | 'medium' | 'large'.
PersonaStateChange type Which user changed, and which parts.
RichPresenceJoinRequest type A friend's "Join game" click and their connect string.
LobbyJoinRequest type An accepted lobby invite: lobby id and inviter.
Overlay class The curated Steam overlay layer. See Overlay.
OverlayDialog type Top level overlay dialog names, open to any string.
OverlayUserDialog type Per-user overlay dialog names, open to any string.
OverlayActivation type Whether the overlay just opened or closed.
Auth class The curated auth ticket layer. See Auth.
AuthTicket type Ticket handle, raw bytes, and their hex form.
ValidateTicketResult type One session validation answer, with the licence owner.
System class The curated machine and client facts layer. See System.
SteamImage type Decoded Steam image: size plus RGBA pixels.
GamepadTextInputOptions type Prompt, mode, and starting text for the gamepad keyboard.
Capture class The curated screenshot layer. See Capture.
ScreenshotReady type Handle and EResult of one finished screenshot.
Controllers class The curated Steam Input layer. See Controllers.
DigitalAction type Pressed state of one digital action, plus whether it is bound.
AnalogAction type Both axes of one analog action, its source mode, and whether it is bound.
Apps class The curated DLC layer, reached as steam.dlc. See DLC.
DlcInfo type One DLC: app id, availability, display name.
flat namespace The whole generated layer: 25 interface classes, enums, consts, struct and callback layouts. See Flat-API.

SteamNative and SteamDispatch are exported because steam.native and steam.dispatch have those types, and because an async flat call needs the pump directly:

const call = steam.ugc.CreateItem(steam.appId, flat.EWorkshopFileType.k_EWorkshopFileTypeCommunity);
const result = await steam.dispatch.callResultStruct<flat.CreateItemResult_t>(
  call,
  flat.layoutOf('CreateItemResult_t'), // picks the win64 or posix table for you
  flat.callbackIdByName.CreateItemResult_t, // optional: rejects a mismatched completion
);
console.log(result.m_eResult, result.m_nPublishedFileId);

How-It-Works explains the pump, the loader, and the offset tables. Recipes has worked examples.

Clone this wiki locally