Repository navigation
Plugin Development
The full plugin author's guide is PLUGIN_DEVELOPMENT.md in the repository. It is the source of truth and is kept in step with the plugin API. This page is a map of it plus the two sections you should read first. A starter plugin lives in the repository's plugin-dev-kit/ folder.
- New plugin checklist
- Trust model — read this first
- manifest.json
- Folder layout
- Two ways to install
- The main-process side:
main.js - The renderer-process side:
renderer.js - Config namespacing
- Verifying your plugin works
Before writing any code, decide these up front — each is cheap to get right at the start and annoying to change later:
-
Isolated or not, and why? Any real capability beyond a single
ipcMainhandler — spawning a process, network access, filesystem writes outside your owndataDir— means"isolated": true(see "Trust model" below). Every first-party plugin with real capabilities uses it now; same-process mode is for genuinely light plugins only. -
Which
category?"channel"(registers a real channel number),"theme"(aregisterThemePackentry),"onscreen"(an overlay/effect, not tuned to), or"system"(background service, remote control, integration, no on-screen presence). This is only a grouping hint for the PLUGINS tab's category tabs, but pick one deliberately — "OTHER" is what you get for skipping it. -
Does it need a
dataDir? Only if you keep real data (a cache, guest uploads, anything beyond your own plugin folder) at the top level ofuserData/. Declaring it lets the app's own userData migration carry it along; if you don't need one, don't declare one. -
Renderer entry point or not?
renderer.js/settings.jsshare the same document and DOM as core and every other plugin — no isolation at all, unlikemain.js. If your UI doesn't need direct DOM integration, prefer a sandboxed iframe or Worker instead of a renderer entry point (see "Trust model" below). -
Does settings UI belong in its own
settings.js? Every first-party plugin now splits its PLUGINS-tab settings panel out ofrenderer.jsinto its ownsettings.js(declared via manifest"settings") so channel- render logic and settings-panel logic don't share a file. Do this from the start rather than retrofitting it later. -
Does it need a cross-plugin service? If another plugin (or core) needs
to call into yours, publish it via
ctx.services/claimServiceKeyrather than having one pluginimportanother directly — plugins can't import each other's files across origins anyway (see "Cross-plugin services" below). -
Read a similar existing plugin first.
plugins/has a first-party example for nearly every shape (channel, theme pack, system/isolated, media source) — copying the closest match's manifest/folder layout beats re-deriving these conventions from scratch.
By default, a plugin's main.js runs via require() in the Electron main
process — full Node/Electron access (ipcMain, child_process, fs, all
of it), the same privilege level as the app's own code. There is no sandbox
between plugins, or between a plugin and the app, in this mode.
Set "isolated": true in manifest.json and your main.js instead runs in
its own real OS process (a forked Node process, no direct Electron access at
all — see "Isolated plugins" below), with Node's own --experimental-permission
flags actually enforcing (not just checking) that it can only read/write the
paths and spawn the processes it declared. This is a genuine process
boundary, not a same-process convention — a plugin that bypasses ctx.fs/
ctx.spawn entirely and calls require('fs')/require('child_process')
directly still gets denied. New plugins with any real capabilities (spawn,
network, filesystem writes beyond their own dataDir) should default to
"isolated": true — every first-party plugin that needs one now does.
Same-process mode still exists for plugins with lighter needs (a single
ipcMain handler, nothing else) where the extra process overhead isn't
worth it, and capabilities are still checked there too — just not by the OS,
only by the same JS-level wrapper as before.
Only install plugins you trust the source of, isolated or not. A ZIP install records a SHA-256 digest outside the plugin folder. Manually copied plugins require an explicit one-time trust decision, and any later byte change blocks all of that plugin's main, renderer, settings, and CSS entry points until the exact new digest is reviewed. This detects replacement or local tampering; it is not an author signature and does not prove who wrote the original ZIP.
Isolation
scopes what a plugin's main.js can touch on disk and which binaries it can
spawn; it does nothing to stop a plugin from, say, exfiltrating data over the
network it already declared a capability to use. It does not isolate
renderer.js, settings.js, or style.css: those intentionally join the
same renderer document as core and can inspect or modify the whole UI and
call the exposed window.api surface. If third-party UI does not require
direct DOM integration, prefer a sandboxed iframe or Worker communicating
through narrow messages instead of a renderer entry point. If you're
distributing a plugin, say clearly what it does and what
it talks to (network requests, spawned processes, file access) so users can
make that call.
See also Plugins, Security and Architecture.
RerunTV is MIT licensed, in beta, and not affiliated with any broadcaster, network or studio. Releases · Issues · Repository