Writing a Plugin
Octarq is extended by plugins, not forks. A plugin is a self-contained feature composed at build time, consisting of:
- A Go module implementing the backend contract
plugin.Plugin - A JS package implementing the frontend contract
UIPlugin(from@octarq/plugin-sdk)
1. Directory Structure
A minimal full-stack plugin repository follows this structure:
your-plugin/├── go.mod # Go module (e.g., github.com/you/octarq-plugin-hello)├── hello.go # Implements plugin.Plugin (+ optional MenuProvider, MCPProvider)└── web/ ├── index.ts # Implements UIPlugin (@octarq/plugin-sdk) └── Page.tsx # React UI page2. Backend Implementation (plugin.Plugin)
Every plugin implements plugin.Plugin and optionally registers routes, models, menus, or MCP tools.
package hello
import ( "net/http" "github.com/octarq-org/octarq/plugin")
type Plugin struct{}
// Name returns a unique, stable ID for the plugin.func (Plugin) Name() string { return "hello" }
// Models returns GORM model structs owned by this plugin.func (Plugin) Models() []any { return nil }
// Mount registers HTTP endpoints on the host router.func (Plugin) Mount(mux plugin.Mux, ctx *plugin.Context) { mux.Handle("GET /api/hello/ping", ctx.Guard(http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(`{"message": "pong"}`)) }, )))}
// Menus defines sidebar entries provided by this plugin.func (Plugin) Menus() []plugin.MenuItem { return []plugin.MenuItem{ {ID: "hello", Label: "Hello", Path: "/hello", Icon: "👋", Category: "Workspace"}, }}
// Compile-time interface assertionsvar ( _ plugin.Plugin = Plugin{} _ plugin.MenuProvider = Plugin{})3. Frontend Implementation (UIPlugin)
The frontend uses @octarq/plugin-sdk to register routes, sidebar items, widgets, and translation dictionaries.
import { lazy } from "react";import type { UIPlugin } from "@octarq/plugin-sdk";
export const helloPlugin: UIPlugin = { name: "hello", routes: [ { path: "/hello", Component: lazy(() => import("./Page")), }, ], menu: [ { id: "hello", label: "Hello World", path: "/hello", icon: "👋", category: "Workspace", }, ],};4. Inter-Plugin Service Registry
Plugins communicate through an in-memory service registry provided on plugin.Context.
- Provide a service:
ctx.Provide("hello.service", myServiceInstance)
- Lookup a service safely:
if svc, ok := plugin.LookupAs[MyService](ctx, "hello.service"); ok {svc.DoSomething()}
Start(ctx context.Context)from optionalStarterinterface runs in a background goroutine after all plugins have mounted, making it the safe entry point for inter-plugin initialization.
5. Composition & Building
Octarq plugins are composed at build time (similar to xcaddy):
# Build custom binary with your Go and JS plugin modulesOCTARQ_PLUGINS='[{"go":"github.com/you/octarq-plugin-hello","npm":"@you/octarq-plugin-hello"}]' make plugin-build- Routes Auto-Gate: Endpoints automatically return
404when disabled in workspace settings. - AutoMigrate Preflight: Database tables are safely resolved and migrated at startup.