Skip to content

Core Plugins & Decoupling

How core features are extracted into plugins and decoupled using platform seams.

Updated View as Markdown

Octarq isolates features into self-contained plugins. This page documents core plugin extraction and the decoupling seams used.

Feature Extraction

Core features like dns, links, and mail live in backend plugins (plugins/) and frontend plugins (web/src/plugins/).

Ownership boundaries

Plugin Model Ownership Responsibilities Services Provided
dns Domain, ProviderAccount, DNS Records Domain verification, DNS zone management, API endpoints under /api/domains dns.manager (plugin.DNSManager), domain.hosts (lookup)
links Link, LinkEvent Dashboard link CRUD under /api/links, redirect engine, analytics links.wrap (HTML link rewrite), links.hostcheck
mail Mailbox, Email, Attachment, SMTPSender Mailbox/SMTP management, inbound email webhook, email dispatch — (triggers OnEmail hooks)

Decoupling Seams

Architectural seams used by plugins:

Service Provider Seam (Context.Provide / Lookup)

When a plugin needs to consume functionality from another plugin (or the core needs it), the target plugin registers its service in plugin.Context during Mount. Other components look it up dynamically:

// Register service (in dns plugin Mount)
ctx.Provide("dns.manager", dnsManagerInstance)

// Consume service (in links plugin or core)
if dns, ok := plugin.LookupAs[plugin.DNSManager](ctx, "dns.manager"); ok {
    dns.Verify(domain)
}

Core does not hardcode menus. Plugins implement the MenuProvider interface:

type MenuProvider interface {
    Menus() []MenuItem
}

The application calls Menus() on all active plugins to build the sidebar navigation dynamically.

Dynamic i18n Namespaces

Plugin translation catalogs (UIPlugin.i18n) are registered dynamically under the plugin’s namespace. Special top-level keys like nav and settings.pluginDesc are deep-merged back into the shared global namespace at startup to support translating navigation labels and settings pages without hardcoding terms in the core.

Static Asset Hosting (Context.HandleStatic)

For plugins that need to serve independent single-page apps (SPAs) or static assets under a specific path prefix, the core provides a generic prefix-based static router seam:

ctx.HandleStatic("/portal", portalDistFS)

Requests under the mounted path prefix are automatically served directly from the provided filesystem, with client-side SPA routing fallback enabled.


Porting or Creating Core Plugins

When moving endpoints or building a new plugin:

  1. Avoid internal/* imports: Plugins should only import the public plugin package. All shared resources (DB, encryption, auditing, config settings) are accessed via plugin.Context.
  2. Handle authorization via Context: Do not hand-authenticate requests within the handler. The core auth middleware authenticates /api/ calls. Instead, check the active organization:
    r, _ := humago.Unwrap(input.Ctx)
    if p.orgID(r) == 0 {
        return nil, huma.Error401Unauthorized("unauthorized")
    }
  3. Specify compile-time assertions: Ensure your plugin struct asserts it implements all declared interfaces:
    var (
        _ plugin.Plugin       = (*Plugin)(nil)
        _ plugin.MenuProvider = (*Plugin)(nil)
    )
Navigation

Type to search…

↑↓ navigate↵ selectEsc close