Skip to main content
Widgets reload every time the overlay loads. Anything in a variable is gone. StreamWizard.state stores a JSON blob per placed widget, so death counters and sub goals survive OBS restarts and stream ends.

The API

Two calls. get() returns your saved object or null; set() replaces it. Spread the old state if you’re only changing one key.

A complete counter

Good to know

  • Per placement: two copies of the same widget on different overlays have separate state.
  • Editor preview has no state: get/set throw there, because the preview isn’t attached to an overlay. Wrap in try/catch or .catch() if you test in the editor a lot.
  • Don’t save on every frame: batch it. Save after a change settles, not inside an animation loop.
  • Older widgets using window.StreamWizard.stateUrl with manual fetch still work; state.get/set is the same API with the plumbing done.

Channel state: StreamWizard.userState

StreamWizard.state is private to one placed widget. StreamWizard.userState is shared across the whole channel, stored key by key, and written by StreamWizard’s servers too — which is the part that matters. A widget only receives events while it is open, so anything you derive from an event is wrong the moment the overlay was closed when it fired. Channel state is still there when the widget comes back.
Keys are 1–64 characters of a-z, 0-9 or _. Values are any JSON up to 8KB, and a channel can hold 200 keys.

Counters: use increment

Never build a counter with get then set — two writers racing (a mod command and a widget, or two open overlays) lose updates. increment adds on the server atomically and resolves to the new value:
A key that doesn’t exist yet starts from 0, so the first increment just works. Incrementing a key that holds a non-number rejects with an error. delete(key) removes a key entirely.

Live updates: subscribe

Every channel-state change — from this widget, another widget, StreamWizard’s servers, or (soon) chat commands — is pushed to open overlays the moment it lands. No polling:
subscribe(key, cb) and onChange(cb) (all keys, sys. included) both return an unsubscribe function. Registering is safe anywhere — in the editor preview there is no socket, so the callback simply never fires. The raw frame also arrives as an onEventReceived with listener streamwizard.user_state and event { key, value, updatedAt }, if you prefer one event handler.

Server-written keys

Keys beginning sys. are written by StreamWizard and are read-only — a widget that could write them could lie about what the channel is doing. This is how to react to something that happened while you were closed. Rather than resetting a total on stream.online — which does nothing if the overlay opens later — record which stream the total belongs to and compare on load: