Package SDK · apiVersion 1
Build your own widgets and themes — without rebuilding the app
OSE picks packages up from a folder: a widget is a widget.json, a JS class and some CSS; a theme is tokens and CSS. The SDK scaffolds a working package, validates it with the same rules as the app loader, and ships window.OSE types for autocompletion.
- No rebuild — just a folder in the user data
- Plain JavaScript and CSS, like any web page
- Manifest validation before install
npx ose-plugin create hello
npx ose-plugin check .
Quick start
Node 18+ is required. The SDK lives in npm as ose-plugin — no need to clone the repository.
npx ose-plugin create hello # widget package in ./hello
npx ose-plugin create plain --theme --name Plain
npx ose-plugin check . # validate before install
npx ose-plugin check config/plugins --json # every package, CI-friendly
npx ose-plugin pack hello # hello.zip for Install from ZIP
The same commands live in the OSE repository without npm: node sdk/create.mjs, node sdk/validate.mjs and node sdk/pack.mjs.
-
1
Create a package
The scaffold already works: manifest, widget class, styles and a README with install steps. Replace the contents with your own.
-
2
Drop it into the packages folder
Development —
config/plugins/<id>/; installed app —%APPDATA%\com.openstreamenvironment.app\plugins\(on other systems, the app data directory). You can skip the manual folder: build an archive withose-plugin packand use Install from ZIP in Settings → Packages. -
3
Reload the page
No restart is needed: manifests are re-read when a page loads. The panel reloads itself when you return focus to it; refresh the OBS source manually. Code and CSS are served from disk, so edits show up right away.
-
4
Add the widget from the library
The card appears in the widget column; a theme shows up in Settings → Appearance marked as a package.
What is inside a package
One folder — one package. The manifest picks the kind: widget.json for a widget, theme.json for a theme.
Widget
The class extends OSE.BaseWidget: the engine creates the element, applies geometry and calls render() on mount and when settings change.
widget.json · widget.js · widget.css · README.md
Theme
--md-*, --font-* and --panel-* tokens plus optional CSS scoped to your data-theme. The theme applies to the overlay, scenes and splash.
theme.json · theme.css · README.md
Widget
The settings schema draws the inspector itself — no need to hand-build fields. Values arrive in this.config.
widget.json
{
"apiVersion": 1,
"kind": "widget",
"id": "hello",
"name": "Hello",
"entry": "widget.js",
"styles": ["widget.css"],
"defaultGeometry": { "x": 40, "y": 42, "w": 20, "h": 10 },
"settings": {
"text": {
"type": "string",
"label": "Text",
"default": "Hello, stream!"
}
}
}
widget.js
(function () {
const OSE = window.OSE;
if (!OSE) return;
class HelloWidget extends OSE.BaseWidget {
render() {
if (!this.element) return;
this.element.textContent =
String(this.config.text || "Hello");
}
}
OSE.defineWidget("hello", HelloWidget);
})();
Settings
The inspector understands seven field types; the default comes from default, and for select it must be one of options — otherwise the app silently falls back to the first option.
string— single-line text;text— multi-line text;number— a number;range— a slider withmin,maxandstep;color— a colour;boolean— a toggle;select— a list of{ "value", "label" }options.
Lifecycle and events
The engine unsubscribes and clears timers when the widget is removed. Events are the same frames the overlay receives: ALERT, CHAT_MESSAGE, RECENT_EVENT, GOAL_UPDATE, STAT_UPDATE, SESSION_STATS, THEME_UPDATE.
onMount() {
this.subscribe(OSE.EVENT_TYPES.ALERT, (alert) => {
this.element.textContent = `${alert.kind}: ${alert.user}`;
});
this.every(() => this.render(), 1000);
}
Also available: this.geometry, this.element, this.canvas and this.ctx, this.startRenderLoop(fps), this.context.readCssVar("--md-primary") and OSE.asset(id, file) for package files.
Theme
A theme appears in the list as plugin:<id>. It cannot be edited from the panel — the package is the source of truth.
theme.json
{
"apiVersion": 1,
"kind": "theme",
"id": "plain",
"name": "Plain",
"tokens": {
"--md-primary": "#7aa2f7",
"--md-surface": "#0b1020",
"--md-on-surface": "#e6edf7"
},
"css": "theme.css"
}
theme.css
Scope rules to your own data-theme, or they will leak into other themes and the control panel.
[data-theme="plugin:plain"] .widget-instance {
border-radius: 4px;
}
Fonts end up on stream, so use only free licences that allow commercial use (SIL OFL and similar) and keep the licence text next to the file.
Validation and types
A manifest mistake is better seen in the terminal than after an app restart with an empty card.
ose-plugin check mirrors the loader rules: id matches the folder, entry and styles exist and stay inside the package, settings use known types, a theme has tokens starting with --. Warnings (for example, default missing from options) do not block the install but usually mean a typo.
npx ose-plugin check .
npx ose-plugin check config/plugins
npx ose-plugin check . --json # for scripts and CI
The SDK ships index.d.ts — global types for window.OSE, BaseWidget and events. Add this to the top of your JS:
/// <reference path="node_modules/ose-plugin/index.d.ts" />
// @ts-check
The editor starts suggesting this.config, hooks and event payloads. Manifests are also described by JSON schemas in sdk/schema/ — add a $schema link and the editor validates widget.json too.
Documentation and examples
- SDK guide — commands, manifest, settings, events and themes.
- Package format — the loader contract and the
/api/pluginsresponse. - App API — the local HTTP server and the WebSocket bus.
- Examples — the ready
helloandlast-donationwidgets and theplain-themetheme. - ose-plugin on npm — the SDK package.
Got a widget idea?
Start with npx ose-plugin create and share a link in the repository issues. Package code runs in its own sandboxed frame with no access to the app interface or its data. The network stays reachable inside the frame, so install only trusted code.