This document outlines the design for composability, dependency contracts, and building custom editions of Octarq.
1. Principles of Composable Plugins
To allow building custom editions, three structural principles are enforced:
- Dependency Contract — Plugins declare what they require; the app refuses to start on an unsatisfiable composition.
- Build-Time Exclusion — An edition can compile out a core feature (e.g., a links-only binary without mail) so that the excluded feature’s code is absent from the binary, rather than just unmounted.
- Frontend Self-Containment — Each feature’s UI (pages, components, API calls, i18n) lives inside its plugin directory and is composed through the same manifest pipeline Pro plugins use.
2. Dependency Declaration & Validation
To enforce dependency safety at startup, the plugin.Info struct has a Requires field:
type Info struct {
Title string
Requires []string // Slice of plugin names this plugin depends on
}For example:
linksplugin declares:Requires: []string{"dns"}mailplugin declares:Requires: []string{"dns", "links"}
During application startup, a preflight validation checks that every mounted plugin’s dependencies are satisfied by the set of registered plugins. If a dependency is missing, the application halts with a clear error.
3. Frontend Feature Self-Containment
To make features truly modular, all frontend code for a feature is located inside its plugin directory (e.g., web/src/plugins/<feature>/):
index.ts— Contains theUIPlugindefinition (including routes, widgets, areas, i18n).pages/&components/— All page and component files.api.ts— The plugin-specific API client methods.i18n— Localization keys merged dynamically under the plugin’s namespace.
This allows completely dropping a feature from the UI bundle by removing its entry in the default manifest (web/octarq.plugins.json).
4. Custom Edition Build Recipes
Core feature plugins (dns, mail, links) can be selectively excluded from both the backend Go binary and the frontend web UI to produce lightweight, custom-tailored editions of Octarq.
Backend Composition
Trimmed editions are defined at the compilation root (typically in main.go). Instead of auto-mounting all plugins, the composition root explicitly selects the plugins to use:
// Example of a links-only composition root
package main
import (
"github.com/octarq-org/octarq/server/app"
"github.com/octarq-org/octarq/server/plugins/dns"
"github.com/octarq-org/octarq/server/plugins/links"
)
func main() {
a, _ := app.New()
a.Use(dns.New()) // Required by links
a.Use(links.New())
a.Run()
}Because of Go’s dead-code elimination, any unreferenced packages (like plugins/mail) are automatically excluded from the final compiled binary by the linker.
Frontend Manifest Composition
Control which UI plugins are bundled by configuring web/octarq.plugins.json or setting OCTARQ_PLUGINS_MANIFEST environment variables during build:
Example (octarq.nomail.json):
{
"plugins": [
"./src/plugins/dns",
"./src/plugins/links"
]
}Build command:
OCTARQ_PLUGINS_MANIFEST=octarq.nomail.json pnpm build