Skip to content

Recording

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

Recording

Since v0.6.0. steam.recording is the curated layer over ISteamTimeline: mark what happened while Steam Game Recording was running, so the player can find the interesting moments again and clip them. It is called recording rather than timeline because the generated ISteamTimeline class already owns steam.timeline.

Two things get marked. An event is a moment, or a range of moments, worth clipping: a boss kill, a death, a lap time. A phase is a longer stretch of play, for example one match or one run, that carries tags and attributes and that the user can jump to in the overlay.

Every marking call is fire and forget. Steam has no result for them, and a user with Game Recording turned off silently records nothing, so nothing here throws for that. Event handles still come back from addEvent and startEvent; they just refer to a recording that does not exist, which is what eventRecordingExists is for.

The Timeline instance is created lazily and cached on the Steam object.

setGameMode

setGameMode(mode: number): void

Tells Steam what the player is doing right now. Steam colours the timeline bar by mode, so a menu or a loading screen is visibly not gameplay. mode is an ETimelineGameMode: 1 playing, 2 staging, 3 menus, 4 loading screen.

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

const steam = init({ appId: 480 });
steam.recording.setGameMode(flat.ETimelineGameMode.k_ETimelineGameMode_Playing);
steam.close();

setTooltip, clearTooltip

setTooltip(text: string, timeDelta?: number): void
clearTooltip(timeDelta?: number): void

The text shown when the user hovers the timeline at this moment. Steam keeps it until it is changed or cleared, so this is state, not an event: set it when the situation changes, not every frame. timeDelta (default 0) is seconds relative to now, and negative points into the past.

steam.recording.setTooltip('Level 3, 2 lives left');

addEvent

addEvent(event: TimelineEvent): bigint

Marks one event and returns its handle. Instantaneous without duration, a range with it. Use this when the event is over by the time you know about it; for something still running, use startEvent.

The handle is 0n when Steam did not record an event, for example because the user has Game Recording off. That is not an error and this does not throw.

import { init } from 'steamwand.js';

const steam = init({ appId: 480 });
const id = steam.recording.addEvent({
  title: 'Boss defeated',
  description: 'Beat the first boss without dying',
  icon: 'steam_achievement',
});
steam.close();

startEvent, updateEvent, endEvent, removeEvent

startEvent(event: Omit<TimelineEvent, 'duration'>): bigint
updateEvent(id: bigint, event: Omit<TimelineEvent, 'startOffset' | 'duration'>): void
endEvent(id: bigint, endOffset?: number): void
removeEvent(id: bigint): void

startEvent opens an event whose end is not known yet. It stays open until endEvent, and Steam ends any still-open event when the game exits. updateEvent replaces the text, icon and priorities while it runs, for example to count the kills in a fight that is still going: every field is sent, so pass the whole event, not only what changed. endOffset (default 0) is seconds relative to now. removeEvent deletes an event that turned out not to matter, whether it came from here or from addEvent.

const fight = steam.recording.startEvent({
  title: 'Boss fight',
  description: 'Fighting the first boss',
  icon: 'steam_combat',
});
// later:
steam.recording.endEvent(fight);

eventRecordingExists

eventRecordingExists(id: bigint): Promise<boolean>

Asks whether Steam actually recorded video around one event. False for every event when the user has Game Recording off, and false for an event outside the recording buffer. This is how a game decides whether to offer the user a clip.

if (await steam.recording.eventRecordingExists(id)) steam.recording.openOverlayToEvent(id);

startPhase, endPhase

startPhase(): void
endPhase(): void

Open and close a game phase. Tags and attributes set while a phase is open belong to it. Starting a phase ends the one before it.

steam.recording.startPhase();
steam.recording.setPhaseId('match-4711');
steam.recording.addPhaseTag('Dust II', 'steam_map', 'Map');
steam.recording.setPhaseAttribute('Score', '16-14');
// ... play ...
steam.recording.endPhase();

setPhaseId, addPhaseTag, setPhaseAttribute

setPhaseId(id: string): void
addPhaseTag(name: string, icon: string, group: string, priority?: number): void
setPhaseAttribute(group: string, value: string, priority?: number): void

setPhaseId names the open phase, at most 63 UTF-8 bytes plus the terminator. It is what phaseRecordingExists and openOverlayToPhase take, so use something your game can recognise later. Two phases may share an id, which groups them.

A tag is a fact about the phase that repeats: the map, the character, the game mode. Steam groups tags by group in the UI. An attribute is a single value per group, not a list, so setting the same group again replaces it; use it for things like the final score. priority (default 0) ranks entries inside a group, higher first.

phaseRecordingExists

phaseRecordingExists(phaseId: string): Promise<PhaseRecording>

Asks what Steam recorded during a phase. All counters are zero when the user has Game Recording off, or when no phase ever carried that id, so this never fails for an unknown phase: it answers with an empty recording.

const r = await steam.recording.phaseRecordingExists('match-4711');
if (r.recordingMs > 0n) steam.recording.openOverlayToPhase('match-4711');

openOverlayToPhase, openOverlayToEvent

openOverlayToPhase(id: string): void
openOverlayToEvent(id: bigint): void

Open the Steam overlay on a phase or on one event, where the user can save a clip. The overlay draws into the game's own renderer, so a plain Node or Electron process gets nothing from these; same caveat as the whole Overlay layer.

Types

TimelineEvent

Field Type Meaning
title string Short label shown on the timeline.
description string Longer text shown on hover.
icon string Steam icon name, for example steam_achievement.
priority number Ranks this event against others at the same moment. Default 0.
startOffset number Seconds relative to now; negative points into the past. Default 0.
duration number Length in seconds. Omit for an instant.
clipPriority number ETimelineEventClipPriority: 1 none, 2 standard (the default), 3 featured.

Valve lists the built-in icon names, and lets you upload your own, at https://partner.steamgames.com/doc/features/timeline

PhaseRecording

Field Type Meaning
phaseId string The phase id that was asked about.
recordingMs bigint Total recorded milliseconds, 0n if nothing was recorded.
longestClipMs bigint Length of the longest clip in milliseconds.
clipCount number Clips the user saved from the phase.
screenshotCount number Screenshots taken during the phase.

Errors

No SteamResultError: nothing in ISteamTimeline returns an EResult. eventRecordingExists and phaseRecordingExists are the only async methods, and they reject with SteamApiCallError when the call could not be completed at all. Every other method returns void or a handle and throws nothing, so a call that did nothing looks exactly like a call that worked.

What this layer does not do

  • Record anything itself. Steam Game Recording is a client feature the user turns on. This layer only annotates what the client records.
  • Read clips back. Steam exposes counters, not files. There is no way to get at the video from here.
  • Leave anything on ISteamTimeline unwrapped. The whole interface is covered; the raw calls stay on steam.timeline if you want them.

Flat API explains the calling convention.

Next: Capture for screenshots, which is the other way a player keeps a moment.

Clone this wiki locally