Skip to content

API reference

Inside a bundle, your code talks to the app through window.DesktopOverlaysAPI. The API is split into namespaces. Each namespace is only present if you request its permission in the manifest. If you do not request a scope, the namespace is undefined, so guard for it or rely on your declared permissions.

js
const api = window.DesktopOverlaysAPI
if (api.audio) api.audio.onUpdate(frame => { /* ... */ })

Methods that return data return a Promise. Methods named onUpdate (and similar) take a callback and keep calling it as the data changes.

Namespaces at a glance

NamespacePermissionPurpose
audiosdk.audioSystem audio FFT frames.
mediasdk.mediaThe now-playing track.
windowssdk.windowsOpen windows and desktop state.
appsdk.appForeground app, fullscreen, display, desktop.
notificationssdk.notificationsWindows notifications.
resourcessdk.resourcesCPU, GPU, memory, disk, and network metrics.
optionssdk.optionsThe user's settings for this overlay.
contentsdk.contentMessages between the bundle's surfaces.
sizesdk.sizeRead, set, and watch the overlay size.
positionsdk.positionRead, set, and watch the overlay position.
mousesdk.mouseCursor position relative to the display and the overlay.
storagesdk.storagePersistent key/value storage shared by the bundle.
usersdk.userThe signed-in Steam user's id.

Coordinates

position and mouse use the same coordinate space as the overlay itself: pixels relative to the top-left of the display the overlay sits on, with displayId telling you which monitor. mouse also gives overlayX/overlayY, which are relative to the overlay box's top-left, so 0, 0 is the overlay's own corner.

audio

Requires sdk.audio. Provides frequency data captured from system audio output, suitable for visualizers.

audio.onUpdate(callback)

Calls callback(frame) for each new frame. frame is a Uint8Array of 256 values from low to high frequency, each 0-255 (like getByteFrequencyData). The bars snap up to beats and fall back gently. Frames arrive about every 16ms (~60fps).

js
const bars = document.querySelectorAll('.bar')
api.audio.onUpdate((frame) => {
  bars.forEach((bar, i) => {
    bar.style.height = (frame[i] / 255 * 100) + '%'
  })
})

media

Requires sdk.media. Reports the Windows now-playing session.

media.get()

Returns a Promise of the current snapshot, or a snapshot with hasSession: false when nothing is playing.

media.onUpdate(callback)

Calls callback(info) whenever the now-playing session changes.

The snapshot shape:

FieldTypeNotes
hasSessionbooleanFalse when there is no media session.
titlestring
artiststring
albumTitlestring
albumArtiststring
trackNumbernumber
albumTrackCountnumber
genresstring[]
playbackStatusstring
isPlayingboolean
appIdstringThe source app.
positionnumberPlayback position.
durationnumberTrack length.
lastUpdatedMsnumberWhen position was last refreshed.
thumbnailstring or nullA data:image/...;base64,... URI of the album art, or null.

Position and timeline only refresh on a seek or a track change. For a live position, interpolate from lastUpdatedMs while isPlaying is true.

js
const snap = await api.media.get()
if (snap.hasSession) title.textContent = snap.title + ' - ' + snap.artist

windows

Requires sdk.windows. Reports open top-level windows and whether the desktop is showing. The app's own overlay windows are excluded from the list.

windows.get()

Returns a Promise of the currently visible top-level windows as WindowInfo[]. Visible means not minimized, not cloaked, and having a title.

windows.getForeground()

Returns a Promise of the current foreground window as WindowInfo, or null on the desktop.

windows.onUpdate(callback)

Calls callback({ windows, foreground, desktop }) whenever the set of windows or the foreground changes. desktop is the same boolean as $.app.desktop in conditions: true when no application window is visible.

WindowInfo shape:

FieldType
titlestring
exestring
xnumber
ynumber
widthnumber
heightnumber
foregroundboolean

app

Requires sdk.app. Gives you the same $.app state that conditions use, readable at runtime.

app.get()

Returns a Promise of { activeProcess, fullscreen, displayId, desktop }. activeProcess is the lowercase foreground executable name, or null on the desktop. displayId is the display this overlay sits on.

app.onUpdate(callback)

Calls callback(state) with the same shape whenever the foreground or desktop state changes, so the bundle can react to the same conditions it declares.

notifications

Requires sdk.notifications. Reports Windows notifications from the Action Center. On first use, Windows asks the user to allow reading notifications. If the user declines, the list comes back empty.

notifications.get()

Returns a Promise of the current notifications as NotificationInfo[], oldest first.

notifications.getLast()

Returns a Promise of the newest notification as NotificationInfo, or null.

notifications.onUpdate(callback)

Calls callback(list) with the full current list whenever a notification is added, updated, or removed.

NotificationInfo shape:

FieldType
idstring
appIdstring
appNamestring
titlestring
bodystring
timestampnumber

resources

Requires sdk.resources. Live system metrics: CPU, GPU, memory, disks, and network adapters. Read from Windows performance counters, enriched with NVML for NVIDIA GPUs. Capacities are bytes, throughput is bytes per second, temperatures are Celsius, usage is a percent (0-100), and memory speed is MHz. Anything Windows will not reveal comes back null or 0 (for example per-core temperatures, and the temperature and usage of non-NVIDIA GPUs).

resources.get()

Returns a Promise of the current snapshot.

resources.onUpdate(callback)

Calls callback(update) with the same snapshot about once a second while subscribed. The reader runs only while a bundle is listening, so there is no cost when nobody subscribes.

The snapshot shape is { cpu, gpu, memory, disk, ethernet }:

cpu is an array (one entry per processor package):

FieldTypeNotes
namestringe.g. "AMD Ryzen 7 5800X 8-Core Processor".
temperaturenumber or nullPackage/thermal-zone reading, or null when unavailable.
usagenumberOverall busy percent, 0-100.
coresarrayPer logical core (see below). cores.length is the core count.
threadsnumberLogical processor count.

Each cores[] entry:

FieldTypeNotes
indexnumberLogical processor index.
usagenumberBusy percent for that core, 0-100.
temperaturenumber or nullPer-core temperature, usually null on Windows.

gpu is an array (one entry per adapter):

FieldTypeNotes
namestring
temperaturenumber or nullCelsius on NVIDIA, null otherwise.
usagenumber or null0-100 on NVIDIA, null otherwise.
memoryobject{ total, used, free } in bytes. used/free are 0 on the non-NVIDIA fallback.

memory is an array, one entry per physical RAM module. total and speed are that module's own; RAM usage is not measurable per stick, so the system-wide used/free are echoed onto every entry:

FieldTypeNotes
namestringModule make/part and slot, or "System Memory".
totalnumberThis module's capacity, in bytes.
usednumberSystem-wide bytes in use (same on every entry).
freenumberSystem-wide bytes free (same on every entry).
speednumberThis module's speed in MHz, or 0 when unknown.

disk is an array (one entry per drive):

FieldTypeNotes
namestringThe drive, e.g. "C:".
typestring"fixed", "removable", etc.
totalnumberCapacity in bytes.
usednumberUsed space in bytes.
speedsobject{ read, write } in bytes per second.

ethernet is an array (one entry per network adapter):

FieldTypeNotes
namestringAdapter name.
speedsobject{ upload, download } in bytes per second.
js
api.resources.onUpdate((u) => {
  cpuBar.style.width = u.cpu[0].usage + '%'
  const gpu = u.gpu[0]
  if (gpu) gpuLabel.textContent = `${gpu.name} ${gpu.usage ?? '?'}%`
  const installed = u.memory.reduce((sum, m) => sum + m.total, 0)
  if (installed) ramLabel.textContent = Math.round(u.memory[0].used / installed * 100) + '%'
})

options

Requires sdk.options. Reads the user's settings for this overlay instance and reacts to changes. See Options for how to define them.

options.get()

Returns a Promise of the current values, keyed by option key.

options.set(key, value)

Sets one option value.

options.update(values)

Sets several option values at once, given an object of key to value.

set and update cannot change shortcut or file options. Those are user-driven only, and writes to them are ignored.

options.onUpdate(callback)

Calls callback(event) when something changes. The event.action field tells you what happened:

actionExtra fieldsMeaning
value-changevaluesOne or more option values changed. values holds the changed keys.
button-clickkeyA button option was clicked.
shortcutkeyA shortcut option's global key was pressed.
editor-statevisible, editModeThe overlay's editor state changed. visible is whether the overlay is shown; editMode is whether the editor panel is open.

The initial state is also sent once when the surface loads.

js
api.options.get().then(apply)
api.options.onUpdate((e) => {
  if (e.action === 'value-change') apply(e.values)
  else if (e.action === 'button-click') doAction(e.key)
})

content

Requires sdk.content. Lets the bundle's own surfaces talk to each other. A message is delivered to every other open surface of the same bundle, but not back to the sender.

content.sendMessage(message)

Sends a message object to the bundle's other open surfaces.

content.onMessage(callback)

Calls callback(message) for messages from the bundle's other surfaces.

size

Requires sdk.size. Reads and changes the overlay's box size.

size.get()

Returns a Promise of { width, height } in pixels.

size.set(width, height)

Resizes the overlay. Expects positive integers. The change is saved and reflected everywhere. If the content declares size bounds (minWidth/maxWidth/minHeight/ maxHeight in the manifest), the value is clamped to them. For fullscreen surfaces the effect is limited, since they cover the display.

js
const s = await api.size.get()
api.size.set(s.width, s.height + 40)

size.onUpdate(callback)

Calls callback({ width, height }) whenever the overlay box is resized, whether by the user dragging a handle, by size.set, or by a content swap. Useful for re-laying-out a canvas.

js
api.size.onUpdate(({ width, height }) => {
  canvas.width = width
  canvas.height = height
})

position

Requires sdk.position. Reads, sets, and watches where the overlay sits. See Coordinates for the coordinate space.

position.get()

Returns a Promise of { x, y, displayId }.

position.set(x, y, displayId?)

Moves the overlay to display-local x, y. The move is smart about monitors: if the point spills past the display's edge, the overlay slides onto the adjacent monitor and displayId updates to match. Pass an optional displayId to make x, y relative to that monitor instead of the overlay's current one (handy for jumping to a specific screen). The change is saved.

js
const p = await api.position.get()
api.position.set(p.x + 100, p.y) // nudge right, crossing to the next monitor if needed
api.position.set(0, 0, 2)        // top-left of display 2

position.onUpdate(callback)

Calls callback({ x, y, displayId }) whenever the overlay is moved or lands on another display.

mouse

Requires sdk.mouse. Reports the cursor position relative to the display and to the overlay. This works on the live desktop even though the overlay is click-through.

mouse.onUpdate(callback)

Calls callback({ x, y, overlayX, overlayY, hovering, displayId }) as the cursor moves over the overlay's display.

FieldTypeNotes
xnumberCursor x, display-local.
ynumberCursor y, display-local.
overlayXnumberCursor x relative to the overlay box's top-left.
overlayYnumberCursor y relative to the overlay box's top-left.
hoveringbooleanWhether the cursor is over the overlay box.
displayIdnumberThe monitor the cursor is on.
js
api.mouse.onUpdate(({ overlayX, overlayY, hovering }) => {
  eye.style.transform = hovering ? `translate(${overlayX}px, ${overlayY}px)` : ''
})

storage

Requires sdk.storage. A small key/value store that persists across restarts and is shared by every instance of your bundle (keyed by the bundle's name and author), so two copies of the same overlay see the same data and it survives reinstalling or updating the bundle. Values must be JSON-serializable.

storage.get(key?)

With a key, returns a Promise of that value (or undefined). With no argument, returns the whole store as an object.

storage.set(key, value)

Stores one value and persists it.

storage.update(values)

Merges several values at once, given an object of key to value.

storage.onUpdate(callback)

Calls callback({ values }) whenever any instance of the bundle writes, including this one. values holds the keys that changed, so instances stay in sync.

js
const saved = await api.storage.get('highScore')
api.storage.set('highScore', Math.max(saved ?? 0, score))
api.storage.onUpdate(({ values }) => {
  if ('highScore' in values) render(values.highScore)
})

user

Requires sdk.user. Exposes the identity of the signed-in Steam user.

user.get()

Returns a Promise of the user's Steam id as a string, or null when Steam isn't available. The id is stable per account, so a bundle can use it to scope per-user state, leaderboards, or saves.

js
const steamId = await api.user.get()
if (steamId) console.log('signed in as', steamId)