Skip to content

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.


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

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.


  • 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_migrations table
  • 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
  • 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

Every extension gets a namespace in the URL space:

URL patternPurpose
/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.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).


Every installed extension has a load_status reflecting its current state:

StatusMeaning
loadedRunning normally. Hooks dispatch, registrations are live.
disabledAdmin has disabled it. Module stays loaded; dispatch is filtered out.
not_loadedDB row exists but loader hasn’t run. Toggle enable to load.
compile_failedSource didn’t compile, or assets/supervisor failed.
manifest_invalidManifest failed validation, or the declared module doesn’t match.
migration_failedA migration raised during install or update.
download_failedRelease tarball couldn’t be downloaded. Usually transient.
install_failedon_install/1 returned an error or raised. Extension is otherwise loaded.
update_failedon_update/2 returned an error or raised. New version is loaded.
no_releaseRepo has no published GitHub releases.
no_repoManifest 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.