DonatonWidget, DonationTimer, ReelWidget, and others in this project.
All widgets are located in a single repository on GitHub.
Files You Need
There is no other global registration needed. Everything is wired through
WIDGET_TYPES and the router.
Step 1 — Settings Class (MyWidgetSettings.tsx)
The settings class defines every user-configurable property of the widget. It extends AbstractWidgetSettings and organises properties into sections (tabs in the UI).
Location: src/pages/MyWidget/MyWidgetSettings.tsx
Available Property Types
Property Constructor Options
Every property accepts at minimum:name— unique identifier (used for JSON serialisation)value— default valuedisplayName— label shown in UI (can be an i18n key)help— optional help tooltip
Step 2 — Widget Component (MyWidget.tsx)
The widget component is the visual element rendered in OBS. It receives typed settings as a prop and uses MobX observer for reactivity.
Location: src/pages/MyWidget/MyWidget.tsx
Note: The widget component itself should be a pure UI concern — it receives settings and optionally a store, and renders. Business logic (WebSocket subscriptions, timers, API calls) lives in the settings class or a separate store.
Step 3 — CSS Module (MyWidget.module.css)
Location: src/pages/MyWidget/MyWidget.module.css
Step 4 — Widget Page (MyWidgetPage.tsx)
The page component is the React Router route target. It loads settings from the API, deserialises them, and renders the widget inside <WidgetWrapper>.
Location: src/pages/MyWidget/MyWidgetPage.tsx
Step 5 — Register in Widget.tsx
Add your widget to the WIDGET_TYPES array so it appears in the UI.
File: src/types/Widget.tsx
First, import your settings class at the top:
WIDGET_TYPES array (insert it anywhere, alphabetical order is optional):
Important: Thenamefield ("my-widget") is used as the URL path segment and as thetypefield in the API. Choose something short and unique.
Step 6 — Add Route in index.tsx
Add a new route so the widget can be served at /my-widget/:widgetId.
File: src/index.tsx
First, import your page component at the top:
createBrowserRouter array:
name you used in WIDGET_TYPES.
Step 7 (Optional) — Store Class
If your widget needs real-time data via WebSocket or API calls, create a MobX store.Real Store — MyWidgetStore.ts
Demo Store — MyWidgetDemoStore.ts
For the settings preview (so the widget appears alive in config):
Complete File Checklist
When you are done, you should have these files:Anatomy of a Minimal Widget (DonatonWidget Reference)
The simplest real widget in this codebase isDonatonWidget. Here’s how it maps to this guide:
Key Architecture Rules
- Settings are reactive — properties use MobX
observable. Reading.valuein anobservercomponent automatically re-renders on change. - CSS modules — every widget gets its own
.module.cssfile. Avoid global styles. - No manual serialisation —
AbstractWidgetSettings.prepareConfig()collects all properties by name/value automatically.Widget.configFromJson()restores them. WidgetWrapperprovides the shell — it setsoverflow: hidden, transparent background, and wires WebSocket command listeners for OBS refresh. Wrap every widget page with it.- i18n —
displayNameandtitlefields can be either Russian strings directly or i18n keys for translation (e.g.,"widget-reel-required-amount"). - Demo/preview — implement
hasDemo() → trueanddemo() → ReactNodeto show a live preview inside the settings panel. Use aDemoStoreif the widget normally requires backend data.