Skip to main content
This guide walks through adding a minimal widget with custom settings, following the same architecture as 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 value
  • displayName — 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
For widgets that need a store (e.g., WebSocket subscriptions), add store initialisation:

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:
Then add an entry to the WIDGET_TYPES array (insert it anywhere, alphabetical order is optional):
Important: The name field ("my-widget") is used as the URL path segment and as the type field 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:
Then add a route inside the createBrowserRouter array:
The route path must match the 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):
Use the demo store in the settings class:

Complete File Checklist

When you are done, you should have these files:
And these files modified:

Anatomy of a Minimal Widget (DonatonWidget Reference)

The simplest real widget in this codebase is DonatonWidget. Here’s how it maps to this guide:

Key Architecture Rules

  1. Settings are reactive — properties use MobX observable. Reading .value in an observer component automatically re-renders on change.
  2. CSS modules — every widget gets its own .module.css file. Avoid global styles.
  3. No manual serialisation — AbstractWidgetSettings.prepareConfig() collects all properties by name/value automatically. Widget.configFromJson() restores them.
  4. WidgetWrapper provides the shell — it sets overflow: hidden, transparent background, and wires WebSocket command listeners for OBS refresh. Wrap every widget page with it.
  5. i18n — displayName and title fields can be either Russian strings directly or i18n keys for translation (e.g., "widget-reel-required-amount").
  6. Demo/preview — implement hasDemo() → true and demo() → ReactNode to show a live preview inside the settings panel. Use a DemoStore if the widget normally requires backend data.