Repository navigation
Plugins
Add a source or an integration your server did not ship with.
A plugin is one piece of code that talks to something outside your server: a
metadata source, a subtitle site, a Discord channel, your company's identity
provider. Obelo runs it inside a sandbox and calls it the same way it calls
anything built in. You install one as a single .zip file, from the web app, and
it takes effect at once — no restart.
Everything on this page happens under Admin → Plugins. Writing your own plugin is a different job, covered in Plugin Development.
The set of things a plugin may do is closed. There are seven kinds, and a plugin may be more than one:
| Kind | What it does for you |
|---|---|
| Metadata provider | Looks up what a title is and what artwork it has (TMDB, MusicBrainz, and so on). |
| Subtitle provider | Finds subtitles for a release and downloads one. |
| Event sink | Tells something outside when something finishes, such as a scan. It sends; it is never asked anything. |
| Web reference provider | Supplies links to read about an item elsewhere. It has no network access at all. |
| Lyric provider | Supplies a track's lyrics. |
| Marker provider | Supplies where a file's intro, recap, credits and preview are. |
| Sign-in provider | Lets people sign in with something other than a local password: an identity provider you run, or a directory such as LDAP. |
A plugin never decides what your media is. Identity comes from your file names and tags. A plugin only decorates, or fetches, or notifies.
A plugin runs in a sandbox with no filesystem, no environment and no sockets of its own. Its only way out is a request the server makes on its behalf, and only to the hosts its manifest lists (see the network allowlist). The one exception is a Sign-in provider that declares it needs to speak a non-HTTP protocol such as LDAP; see Directory sign-in.
The binary carries nine plugins of its own, and they are ordinary plugins that were placed for you on first boot:
| Plugin | Kind | Reaches |
|---|---|---|
| The Movie Database (TMDB) | Metadata provider (video) |
api.themoviedb.org, image.tmdb.org
|
| OMDb API | Metadata provider (video) | www.omdbapi.com |
| TheTVDB | Metadata provider (video) | api4.thetvdb.com |
| AniDB | Metadata provider (video) | api.anidb.net |
| MusicBrainz | Metadata provider (music) |
musicbrainz.org, coverartarchive.org
|
| fanart.tv | Metadata provider (video and music) | webservice.fanart.tv |
| TheAudioDB | Metadata provider (music) | www.theaudiodb.com |
| OpenSubtitles | Subtitle provider (video) |
api.opensubtitles.com, www.opensubtitles.com
|
| OpenID Connect | Sign-in provider | the issuer you type in |
In a shipped plugin's dialog, Installed from reads Shipped with Obelo where an uploaded plugin would say Uploaded. They are re-placed on every boot.
Enrichment waits for consent. The server makes no outbound enrichment calls
until consent is granted. Official builds and the published Docker image carry default TMDB and
fanart.tv keys, which your own key (BYOK) overrides; a self-built image or a
build from source carries none, so those sources do nothing until you supply a key. Other sources need their
own key, and MusicBrainz needs to be switched on. Keys for these
are entered under Admin → Metadata Providers and Admin → Subtitle
Providers, or seeded from environment variables on first boot (for example
OBELO_TMDB_API_KEY and OBELO_FANART_TV_API_KEY); see
Configuration.
Removing a shipped plugin is not permanent. If you uninstall one, its row stays in the list as Removed, with a Reinstall the shipped version button. It will not come back on its own at the next boot. The button is in that row's dialog.
Open Admin → Plugins. There are three ways in, and a package is a single
.zip holding manifest.json, the module, and optionally plugin.sig.json.
Under Install from a package, choose the file in Plugin package and press Install.
Under Install from a URL, paste the address in Package URL and press Fetch and install.
A plugin is code this server will run, so an address on your own network is refused. A name that resolves to a private, loopback or link-local address, or that cannot be resolved at all, is turned away. When the name resolved to your own network, the message tells you to upload the package instead. That is a deliberate difference from the rest of Obelo, where pointing at a box on your LAN is normal. If the plugin lives on your LAN, download it and upload the file.
A catalog is a JSON index of plugins that someone published at a URL. Obelo ships with none and recommends none. Under Catalog, put its address in Catalog URL and press Save; the page then gains an Installed / Browse tab bar. Clear the address to lose it again. Each Browse entry has an Install button, which fetches its package under exactly the rules of a pasted URL.
Everything a Browse entry says is the index author's claim: a publisher is always labelled Published by (claimed), whether or not you have pinned a key. If the catalog will not load you get a note; the upload and URL installs keep working.
The server checks, and refuses with a sentence that says which, before anything is written to disk:
| Refusal | Means |
|---|---|
| Not a package | Not a readable .zip, over the size cap, a member is encrypted or appears twice, a file is missing (manifest.json at the root), or there is a file the package should not hold. |
| Bad manifest |
manifest.json is not valid JSON, or claims something the server does not accept, such as an unknown kind. |
| Wrong API version | The plugin was built for another plugin API version. The message names which side must move: upgrade the server, or get a newer plugin. |
| Already installed (409) | The id is taken by a plugin on disk, or by one this server ships. Uninstall the old one first. |
| Bad module | The module would not compile or start in the sandbox. Nothing is left behind. |
| Source refused | A URL install could not be reached, answered an error, was too large, or resolved to your own network. |
Signature (422, PLUGIN_SIGNATURE) |
You have pinned publisher keys and this plugin does not satisfy them. See Signing. |
A refused install leaves nothing on disk and nothing in the database, so you can simply try again.
Each plugin is one row in the list, with its name, version and a status:
| Status | Means |
|---|---|
| Running | Loaded and being called. |
| Switched off | You disabled it. It is not registered at all. |
| Stopped by this server | The server refuses to call it: it would not load, or it failed three times in a row, or repeatedly tried to reach a host it had not declared. The stop and its counts are held in memory: they reset on Re-enable or when the server restarts. |
| Removed | A shipped plugin you uninstalled. |
A red dot beside the status means the plugin has a last error recorded.
Press the pencil (Edit the plugin) to open its dialog:
- Provides lists its kinds. Installed from says where it came from: Shipped with Obelo, Uploaded, the URL it was fetched from, or Placed in the data directory by hand. Installed shows when. Signed by appears only when a signature was verified against a key you pinned.
- A plugin that declares its own settings shows them as a form, with its own labels and help text. Press Save settings. A secret field shows that a value is on file and never shows the value; leave it alone and it stays, or clear it explicitly.
- Disable / Enable is your switch. It survives a restart.
- Re-enable appears only when there is something to forgive (a recorded failure or last error). It clears the failure counts and reloads the module from disk, so a fixed file gets a genuine second try. It does not touch your Enable/Disable switch.
- Uninstall removes the plugin's files, its settings and the small private store it kept, and its rows on the Metadata Providers, Subtitle Providers and Event Sinks pages, its place in the lyric order, and any per-library overrides that named it. A library that had it as its authoritative provider goes back to inheriting. It does not delete artwork or subtitles the plugin produced; those belong to your library.
Stopped by this server and a last error are two different facts. A plugin can report an error and keep running; for example a metadata provider that says your API key was rejected. That sentence appears in the dialog, and the next call that works clears it. Only Stopped by this server means the server has given up calling it, and Re-enable is how you say "I fixed it, try again".
For an event sink, the counters live on Admin → Event Sinks, under Delivery since this server started: Delivered climbing means the receiver is taking messages, Failures climbing means the target refuses or cannot be reached, Dropped climbing means it cannot keep up. All three flat means nothing has happened, or the sink is not subscribed to it.
The Plugins page is for getting plugins onto the server. What a plugin does, the URL a sink posts to, the events it hears, the key a provider uses, is on the page for its kind, in the same card as the built-in beside it: Admin → Metadata Providers, Subtitle Providers and Event Sinks. Lyric providers have an order on the Plugins page under Lyric provider order, which appears once at least two lyric providers are enabled: they are asked from the top, and the first timed answer that fits the track wins.
Every plugin's manifest lists the exact hosts it may reach, in network.hosts. It
has no wildcards, no schemes and no ports, and matching is exact. A plugin with
no list makes no outbound requests at all. The server checks it from the file on
disk on every request; nothing the plugin says at run time can widen it.
Two things to know as an operator:
- The URL you type is trusted. The host of a URL you type in a provider's configuration is permitted alongside the manifest's list, and is not held to the "no private address" rule. That covers Target URL on an Event Sink (such as your own Discord relay), Base URL override on Metadata Providers and Base URL (advanced) on Subtitle Providers. Pointing one at a receiver on your LAN is the point. The only other host trusted this way is the issuer of an OpenID Connect provider. A URL-like field in a plugin's own settings form on the Plugins page gets no such exemption.
- A refused request is a violation. A plugin that keeps reaching for hosts it did not declare is stopped by the server after three, with the host it reached for on its row. Re-enable or a server restart clears that count. During sign-in, refused requests made while checking a password, starting a redirect sign-in or exchanging the code do not count; those made during the scheduled re-check and token refresh do.
To see what a plugin may talk to, read its manifest, or its entry in the table above for the shipped ones. That is the whole value of the list: you can read it.
Signing is optional, both for plugin authors and for you. Obelo runs no catalog, hosts no registry and vouches for no publisher. A server trusts a publisher because you pasted their key, and for no other reason.
Under Publisher keys:
| Pinned keys | What happens on install |
|---|---|
| None (the default) | Nothing is verified. Signed and unsigned plugins install identically, and the server records no publisher: it did not check, so it does not claim to know. The card says No keys are pinned, so plugin signatures are not checked. Any plugin you install will be installed. |
| One or more | Every install must carry a signature naming a pinned publisher and verifying under that publisher's key. Anything else is refused as PLUGIN_SIGNATURE, naming the publisher the plugin claimed. |
There is no warn-only setting; you pin a key to stop something.
To pin one, get the publisher's name and their base64 ed25519 public key from the publisher, type them into Publisher and Public key, and press Pin. Unpin removes it. Each pinned key shows its short key id, so you can compare it with what the publisher advertises.
- An installed plugin is never re-verified. Pinning a key is a decision about future installs; re-checking what is on disk would stop plugins that were running.
- A plugin that verified shows Signed by in its dialog. A plugin with no such line was not checked, which is not the same as being untrusted.
- A signature covers the manifest and the module, not the zip, so where a package came from adds nothing to the trust decision; the keys you pinned decide it.
There are no environment variables for plugins, signing or catalogs. All of it is set in the web app and stored in the database. The environment variables that touch plugins are the first-boot seeds for the shipped providers (such as the metadata keys and base URLs, the OpenSubtitles key and enrichment consent) and the key-rotation switch; see Configuration for the full list.
A Sign-in provider lets people sign in with something other than a Local password. Your admin account stays a local password, and a Local password is always tried first.
Admin → Plugins shows these cards, each when its own condition holds:
- Sign-in order: appears only when a password-flow Sign-in provider (such as LDAP) is installed and enabled. For password sign-in, the Local password is tried first, then each Sign-in provider from the top. The first to accept signs the person in. Reorder with Move up and Move down.
- Sign-in with another service: appears when a redirect Sign-in provider is installed and enabled, which the shipped OpenID Connect plugin is by default. Each becomes a Sign in with name button on the login screen once it is configured, and is marked not configured yet until then. It also says whether the identity is verified against the issuer's signing keys or not independently verified; the latter means the server takes the provider's word. Sign-in on a TV or an iPad goes through a code approved from a signed-in browser.
- Group mapping: name: one per Sign-in provider; see below.
The shipped OpenID Connect plugin works with an identity provider you run, such as Authentik, Keycloak or Authelia. Open its dialog and fill in:
| Field | What to put |
|---|---|
| Issuer URL (required) | The issuer your identity provider names in its discovery document, such as https://auth.example.com/application/o/obelo/. |
| Client ID (required) | The client you registered for this server. |
| Client secret | Leave empty for a public client. |
| Scopes | Space-separated; default openid profile email groups. openid is always asked for. Add offline_access if your provider only issues a refresh token for that scope. |
Register exactly one redirect URI with your identity provider: this server's
origin followed by /sign-in/callback, for example
https://obelo.example.com/sign-in/callback. It must be the same scheme, host,
port and path your browser uses to reach the server, with no wildcards. The server
has no configured external address: it builds the URI from the address the browser
arrived on, so use the address people really use. If you sit behind a proxy, see
Reverse Proxy.
Every ID token is verified against the issuer and client id you typed: signature, issuer, audience and expiry. The subject and the groups come from the verified token.
A password-flow Sign-in provider, such as one for an LDAP directory, is a plugin you install, since none ships. Because it must speak a non-HTTP protocol, it declares that it needs a socket, and the server then adds four settings to its form after its own:
| Setting | Meaning |
|---|---|
Directory address (socket_address) |
The host:port of your directory. The only address the plugin can reach.
|
Encryption (socket_tls) |
tls (the default), starttls or none. |
Allow unencrypted connection (plaintext passwords) (socket_allow_plaintext) |
Shown with a warning. Needed for none, and to skip a StartTLS upgrade: without it, a StartTLS connection refuses a second unencrypted write, or one large enough to carry a password, before the upgrade. |
Trusted CA certificate (PEM) (socket_trusted_ca) |
A PEM CA to verify the directory against instead of the system's. Empty uses the system's. |
The server dials, encrypts and verifies the certificate itself; the plugin only sees plaintext of its own protocol. Connections last for one call.
Each Sign-in provider gets a Group mapping card. Each row is a Group, a Role (Member or Admin), and for a Member the Libraries they get. Add group adds a row, Remove deletes one, Save stores them.
- The mapping is applied at every sign-in and at every re-check between sign-ins.
- It never changes anyone who has a Local password.
- With no groups mapped, the provider changes nobody's access.
- Re-checks default to every 24 hours; Re-check every N hours changes it, for providers that can be asked between sign-ins. Otherwise the card says This provider cannot be asked between sign-ins and the mapping only applies at sign-in or when you press Re-sync now.
- Re-sync now asks the provider about everyone at once and reports how many were asked, re-mapped, failed and signed out.
- A provider that says a person is gone or disabled signs them out everywhere. A provider that merely cannot be reached changes nothing: everyone keeps their last known access, and the card flags that re-checks are failing.
Uninstalling one deletes every sign-in it provides, so it asks first. The confirmation lists every user who has no other way to sign in; those users are deleted, with their watch history, and this cannot be undone. The button reads Delete N users and uninstall. Everyone else keeps their account and loses only that sign-in. If nobody would be deleted, it says so and the button is simply Uninstall.
Plugins are WebAssembly modules; the full author's guide, including the manifest, the sandbox rules, the Go SDK and a worked example, is Plugin Development.
Getting started
Reference
Going further
Plugins
Sharing with another household
Reaching it from away
When it breaks