Installing extensions
Extensions are managed at Admin → Extensions.
Installing an extension
Section titled “Installing an extension”To install an extension, click Install from URL and enter the URL of its manifest.json. This is typically a raw.githubusercontent.com URL pointing to the manifest at the root of the extension’s repo:
https://raw.githubusercontent.com/<user>/<repo>/main/manifest.jsonNexus then:
- Fetches and validates the manifest
- Derives the GitHub repo from the URL or the manifest’s
repositoryfield - Fetches the latest GitHub release for that repo
- Creates a database row for the extension with
load_status: "not_loaded" - Downloads the release tarball, compiles it, runs migrations, copies assets, starts any background processes, and registers all surfaces
- Calls the extension’s
on_install/1callback
If any step fails, the database row persists with the relevant failure status — the extension appears in the list with a red status pill. The admin can retry by toggling the extension off and back on after the underlying issue is fixed.
The extension list
Section titled “The extension list”Each extension appears as a card showing:
- Name and installed version
- Status pill — green for
loaded, amber for warning states, red for hard failures, grey fordisabled - Description and author
- Action buttons — Install Updates (when a newer version is available) and Manage
The extension page
Section titled “The extension page”Clicking Manage opens the per-extension page. It shows:
- Identity strip — name, version, status, enable/disable toggle, and the
…overflow menu (View repo, View homepage, Sync from GitHub, Run migrations, Uninstall) - Status banner — only visible when the extension is not in the
loadedstate. Shows the load status, a recovery hint, and the specific error message - Registered admin panel — if the extension declared one
- Settings form — auto-rendered below the admin panel whenever the extension has any
settings_schemafields - Runtime registrations panel — under the Advanced collapsible section
Runtime registrations panel
Section titled “Runtime registrations panel”This is the primary diagnostic tool for extension issues. It shows a side-by-side comparison of what the extension’s manifest declared and what Nexus currently has wired up at runtime.
Each row covers one surface kind (hooks, slots, routes, right_widgets, toolbar_buttons, profile_tabs, digest_sections, notification_types, admin_panel, explore, side_data).
- Items in both columns mean the manifest declaration and runtime registration match — the surface is working
- Items only in the left column (declared but not registered) mean the manifest lists something the extension’s code hasn’t registered — the surface won’t function
- Items only in the right column (registered but not declared) mean the code registered something the manifest didn’t declare — a warning is shown
A warnings list at the bottom surfaces all registration-without-declaration mismatches with the specific message and what was actually declared.
Run migrations
Section titled “Run migrations”The Run migrations option in the … overflow menu is a recovery tool for when an extension’s database migrations have gotten out of sync. It runs any pending migrations for the extension — migrations already recorded in schema_migrations are skipped, so it is safe to call at any time.
Use this when an extension is loaded and functional but its tables are missing or out of date, typically after a failed update or a manual schema change.
Enabling and disabling
Section titled “Enabling and disabling”The enable toggle on each extension’s page controls whether the extension is active. Disabling an extension:
- Filters it out of hook dispatch — its handlers stop firing
- Stops its supervised child processes
- Leaves its module, migrations, and database tables in place
Re-enabling triggers a fresh load through the full loader pipeline.
Updating
Section titled “Updating”When a newer GitHub release exists for an installed extension, an Install Updates button appears on the extension’s card. Clicking it:
- Fetches the new release’s manifest
- Stops the old extension’s supervisor, unloads the old module
- Downloads the new tarball, recompiles, runs any new migrations, re-registers all surfaces
- Calls the extension’s
on_update/2callback with the old and new version strings
If the loader pipeline fails during an update, the old version has already been unloaded. The extension lands in a failure load_status until the issue is resolved.
Uninstalling
Section titled “Uninstalling”Click Uninstall in the extension’s … overflow menu. Nexus:
- Calls
on_uninstall/0— the extension’s last chance to clean up external resources (errors here are warnings, not blockers) - Cancels pending Oban jobs whose worker module names start with the extension’s root module
- Rolls back migrations in reverse order
- Unloads the module and stops the supervisor
- Deletes the extension’s file storage directory (
/app/uploads/extensions/<slug>/) and the tarball cache (/app/uploads/extensions/.cache/<slug>/) - Removes the database row
- Cleans up saved layout config referencing the extension
Data in database tables that weren’t dropped by migrations (due to non-reversible migration operations or rollback failures) will remain. These can be dropped manually.
Force-uninstall
Section titled “Force-uninstall”If a normal uninstall fails — the request returns a 500, or the extension’s DB row gets stuck — use Force uninstall from the … overflow menu. Force-uninstall is more aggressive:
- Skips
on_uninstall/0— the callback doesn’t run - Skips migration rollback — your tables stay in the database and must be dropped manually if you want the schema cleaned up
- Still cleans up the supervisor, Oban jobs, file storage, the tarball cache, the DB row, and layout config — best-effort, with warnings on failure
Force-uninstall is the escape hatch for when the extension’s code is broken enough that running it during uninstall would itself fail.
Boot behaviour
Section titled “Boot behaviour”On every Nexus restart, all enabled extensions are reloaded. The loader pipeline runs for each — compiling, replaying migrations (already-applied ones are safely skipped), and re-registering all surfaces. The on_install/1 and on_update/2 callbacks do not run on boot.
Tarball caching means most boots don’t hit GitHub at all. Every successful download is cached to the uploads directory. On boot, the loader checks for a cached tarball before attempting a network fetch:
- First boot after install — downloads from GitHub, caches, compiles, registers.
- Subsequent boots — cache hit, extracts, compiles, registers. No network request.
- After an update — downloads the new version from GitHub, caches it, compiles, registers.
GitHub is only involved at install time and update time. Routine restarts are network-free.
This means:
- Boot time scales with installed extension count — the compile pass is still required every boot, but the network round-trip is gone for cached versions. Compile time is roughly seconds per extension.
- GitHub availability only matters at install and update time — not at routine restarts. If GitHub is unreachable during a restart, already-cached extensions still load. Only fresh installs or updates require GitHub to be reachable.
- Any in-memory state in extension processes is reset on each boot — persist anything that needs to survive a restart to the database.