-
Notifications
You must be signed in to change notification settings - Fork 2
Module API
🌐 English · Deutsch
Everything a module can register with MediaForge, in one place. The full
reference — with a runnable example module per entry point — lives in
.examples/thirdparties/README.md
in the repository; this page is the map.
A module is a folder under web/thirdparties/<name>/ with a register(app)
function. Everything below is called from there.
| Function | Module | What it adds |
|---|---|---|
register_thirdparty |
web.thirdparties.registry |
The module itself: menu entry, settings card, dashboard widget, extra_settings fields, enable toggle. See "Where a settings card lands" below |
register_provider |
providers |
A content source (site) with its own series/season/episode classes |
register_search_source |
search |
An additional source for the global search |
register_home_feed_source |
home_feed |
Rows and a chip for that source on the new home page |
register_hoster |
extractors |
A hoster/extractor that resolves an embed URL to a stream |
register_site_mirrors |
mirrors |
Mirror domains for a site, with automatic failover |
register_cineinfo_source |
web.cineinfo.registry |
A metadata source next to TMDB |
register_monitor_site |
web.uptime_monitor |
A card on the UpTime dashboard, probed like the built-in sites |
register_notification_channel |
web.thirdparties.registry |
A notification channel next to Telegram/Pushover/Discord/ntfy |
register_event_hook |
web.thirdparties.registry |
A callback on app events (download finished, queue item failed, …) |
register_background_worker |
web.thirdparties.registry |
A worker thread started and stopped with the module |
register_backup_category |
web.backup |
Own data in the Full/Selective backup |
register_sensitive_keys |
web.db |
Settings keys that are encrypted at rest |
register_ui_pref_key |
web.db |
A per-account UI preference |
register_restart_handler (web.restart) is not part of this API — it is
core-internal and only called by web/app.py.
settings_host picks the page; the page picks the spot. settings_tab is a
request the host may override, applied at registration time by
registry._placed_tab(), so everything downstream sees the real placement:
settings_host |
Card ends up | settings_tab |
|---|---|---|
"integrations" (default) |
Always the Third Party tab | Ignored, silently |
"notifications" |
Always a tab of its own | A built-in channel id or the bare default becomes module_<item_id>; a custom id is kept |
"monitoring" |
Always a tab of its own | Same rule |
"settings" |
Module Manager → Module Settings | Ignored (that page groups by host) |
"What have my modules added to this page?" has one answer per page, and it stops being an answer as soon as a module can hide on the CineInfo tab or inside Telegram's panel. A module that wants its own tab picks a host that gives it one, not a tab id.
Independently of that, every module card is also listed on Module Manager →
Module Settings, grouped by host. Not a copy: both places drive the same
/api/settings/thirdparty/<id> API.
A "secret" field, a key in MODULE_SENSITIVE_SETTINGS and anything passed to
register_sensitive_keys() is encrypted in the database, and the generic
settings API returns a mask (registry.SECRET_MASK) instead of the value — a
PUT carrying the mask back means "unchanged". This holds for every key
MediaForge knows to be sensitive, not just for fields declared as "secret",
so a secret rendered as a plain text field is masked too. Inside the module
nothing changes: get_setting() decrypts as always. If you render such a value
on a page of your own, do the same — never put a stored secret into the HTML.
Reuse the existing design vocabulary rather than shipping your own CSS:
-
Form controls (
forms.css):.chb-mainfor checkboxes,.togglefor on/off switches,.mf-segmented,.mf-multiselect,.mf-chip. -
Layout and content (
mf_components.css, loaded globally):.mf-search,.mf-toolbar,.mf-poster-grid,.mf-timeline,.mf-progress,.mf-empty,.mf-pagination-bar. -
Colours (
variables.css): never hardcode a hex value. Status pills use the pair--success+--success-bg(same for--warning,--error,--info); both halves are defined per theme. -
Multi-select (
mf_multiselect.js, loaded on every page): putdata-mf-multiselecton a.mf-multiselectroot and open/close, the trigger's summary label, outside-click/Escape and the positioning (it stays unclipped inside scrolling containers) are handled for you — no init call, so markup rendered later by JS works too. Word the label withdata-none-label/data-many-label/data-max-namesand listen formf-multiselect-change/mf-multiselect-closeon the root (detail: {values, labels}, bubbling). Helpers:window.mfMultiSelect.values(),.labels(),.refresh(),.open(),.close(),.closeAll(). -
Escaping (
mf_escape.js, loaded on every page):window.mfEscape()for anything that goes into HTML — it is quote-safe, so it covers attributes too — andwindow.mfSafeUrl()forhref/src. Do not write your own escaper. -
Polling (
mf_poll.js):window.mfPoll(fn, ms)instead ofsetInterval, so your timer pauses while the tab is hidden.
Module templates use {{ _('...') }} like the core does; a module brings its
own catalogue under <module>/translations/<locale>/LC_MESSAGES/. After
editing a .po you must run pybabel compile — without it the change has no
effect at runtime.
🇬🇧 English
Users
- Installation
- Getting Started
- Migration from AniWorld
- Configuration
- Web UI
- Download System
- Download History
- AutoSync
- Calendar
- Library
- Authentication
- Notifications
- Integrations
- SyncPlay
- Anime4K Upscaling
- Encoding
- Modules
- Theme Packs
- Backup
- Docker
- Supported Sites
Developers
🇩🇪 Deutsch
Benutzer
- Installation
- Erste Schritte
- Umzug von AniWorld
- Konfiguration
- Web-UI
- Download-System
- Download-Verlauf
- AutoSync
- Kalender
- Bibliothek
- Authentifizierung
- Benachrichtigungen
- Integrationen
- SyncPlay
- Anime4K-Upscaling
- Encoding
- Module
- Theme-Pakete
- Backup
- Docker
- Unterstützte Seiten
Entwickler