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 serialisationAbstractWidgetSettings.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. i18ndisplayName 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.