Overview
Extensions add functionality to Nexus — new pages, sidebar widgets, composer tools, digest email sections, background workers, and more. They are installed and managed by admins from Admin → Extensions.
The in-VM model
Section titled “The in-VM model”Nexus extensions use an in-VM model. When an admin installs an extension, Nexus:
- Fetches the release tarball from GitHub
- Compiles the extension’s Elixir source directly into the running BEAM VM
- Runs any migrations through Nexus’s own
Repo - Starts any background processes under Nexus’s supervisor tree
- Registers hooks, routes, slots, and other surfaces in an in-memory ETS registry
- Copies the JavaScript bundle to a served path and injects it into every page as a
<script>tag
There is no separate service to deploy, no Docker networking, no inter-service authentication, and no webhook delivery. Event hooks are direct function calls into the loaded extension module.
This model means:
- Extensions share Nexus’s dependency tree —
Ecto,Req,Jason,Oban,Image,Phoenix.PubSub, and everything else Nexus uses is available without any additional declaration - Extensions share Nexus’s database and can define their own tables using
Nexus.Repo - Extensions run with the same privileges as Nexus itself — installing an extension is a significant trust decision
What an extension is
Section titled “What an extension is”An extension is a GitHub repository containing four things:
manifest.json— declares what the extension contributes- An Elixir module implementing server-side behaviour
- An optional JavaScript bundle implementing browser-side behaviour
- A README
Extensions are distributed as GitHub releases. The admin installs by providing a manifest URL; Nexus fetches the latest release tarball, compiles it, and loads it.
What extensions can do
Section titled “What extensions can do”Server-side (Elixir)
Section titled “Server-side (Elixir)”- Hooks — subscribe to forum events (
post_created,user_registered, etc.) and run code when they fire - Routes — register Elixir plug routes at
/ext/<slug>/api/...to handle API requests from the extension’s frontend - Migrations — define database tables using Ecto migrations, run through Nexus’s shared
schema_migrationstable - Background workers — run supervised processes (GenServers, schedulers) and Oban jobs
- Digest sections — contribute content blocks to scheduled digest emails
- Side-data — persist structured data attached to posts, replies, or users (submitted from the composer via the
attach()flow) - Notifications — send notifications to users via web, email, or push
Browser-side (JavaScript)
Section titled “Browser-side (JavaScript)”- Routes — full-page SPA routes mounted at
/ext/<slug>/... - Slots — React components injected into specific positions in Nexus’s UI (currently: below post bodies, in the profile sidebar)
- Admin panel — a custom page in the admin sidebar
- Explore entry — a navigation item in the left sidebar’s Explore section
- Right widgets — panels in the right sidebar, scoped to specific pages
- Toolbar buttons — buttons in the post and reply composers
- Profile tabs — additional tabs on user profile pages
- User, account, and post actions — items in the user card popover, the account dropdown, and the post
…menu
Extension pages
Section titled “Extension pages”Every extension gets a namespace in the URL space:
| URL pattern | Purpose |
|---|---|
/ext/<slug>/... | SPA page routes (served by Nexus’s SPA shell, your JS bundle resolves them) |
/ext/<slug>/api/... | API endpoints (handled by your Elixir plug routes) |
/ext/<slug>/assets/... | Static assets (JS bundle, logo, banner, other bundled files) |
The manifest
Section titled “The manifest”The manifest.json is the contract. Every surface an extension contributes — every hook, slot, route, widget, toolbar button, profile tab, digest section — must be declared in the manifest before Nexus will wire it up. The manifest is validated at install time; unknown or malformed fields cause the install to fail with specific error messages.
The manifest also declares the extension’s identity (name, slug, version, module), metadata (description, author, license, tags), and settings schema (fields the admin configures per-installation).
Load status
Section titled “Load status”Every installed extension has a load_status reflecting its current state:
| Status | Meaning |
|---|---|
loaded | Running normally. Hooks dispatch, registrations are live. |
disabled | Admin has disabled it. Module stays loaded; dispatch is filtered out. |
not_loaded | DB row exists but loader hasn’t run. Toggle enable to load. |
compile_failed | Source didn’t compile, or assets/supervisor failed. |
manifest_invalid | Manifest failed validation, or the declared module doesn’t match. |
migration_failed | A migration raised during install or update. |
download_failed | Release tarball couldn’t be downloaded. Usually transient. |
install_failed | on_install/1 returned an error or raised. Extension is otherwise loaded. |
update_failed | on_update/2 returned an error or raised. New version is loaded. |
no_release | Repo has no published GitHub releases. |
no_repo | Manifest URL didn’t resolve to a user/repo pair. |
Load status and the specific error message are visible on each extension’s page in Admin → Extensions → Manage.