Writing a plugin
Writing a plugin consists of several steps, each described below.
1. Create definition.yaml
Section titled “1. Create definition.yaml”apiVersion: fundament.io/v1kind: PluginDefinitionmetadata: name: my-plugin displayName: My Plugin version: v1.0.0 description: Does something useful author: My Team license: Apache-2.0 icon: puzzle-piece tags: - examplespec: permissions: capabilities: - internet_access rbac: - apiGroups: ["my-api.io"] resources: ["myresources"] verbs: ["get", "list", "watch"]
menu: project: - crd: myresources.my-api.io list: true detail: true icon: pencil-on-square
customComponents: MyResource: list: myresources-list.html detail: myresources-detail.html
allowedResources: - group: my-api.io version: v1 resource: myresources verbs: [get, list]
uiHints: myresources.my-api.io: statusMapping: jsonPath: ".status.phase" values: "Ready": badge: success label: Ready "Failed": badge: danger label: FailedcustomComponents maps a CRD kind to the HTML files your plugin ships under
console/. It is optional: any menu entry without a custom component renders the
console’s generated read-only list and detail views from the CRD schema, so add
customComponents only for kinds that need write actions or a bespoke layout.
allowedResources is the allowlist the console host enforces on every
fundament.k8s.list / .get call the plugin makes — see
Custom UI and Console integration for the
full story.
2. Implement the plugin
Section titled “2. Implement the plugin”package main
import ( "context" "fmt" "log"
"github.com/fundament-oss/fundament/plugin-sdk/pluginruntime")
type MyPlugin struct { def pluginruntime.PluginDefinition}
func (p *MyPlugin) Definition() pluginruntime.PluginDefinition { return p.def}
func (p *MyPlugin) Start(ctx context.Context, host pluginruntime.Host) error { host.ReportStatus(pluginruntime.PluginStatus{ Phase: pluginruntime.PhaseInstalling, Message: "setting up", })
// Do setup work...
host.ReportReady() host.ReportStatus(pluginruntime.PluginStatus{ Phase: pluginruntime.PhaseRunning, Message: "operational", })
<-ctx.Done() return nil}
func (p *MyPlugin) Shutdown(_ context.Context) error { return nil}
func main() { def, err := pluginruntime.LoadDefinition("definition.yaml") if err != nil { log.Fatal(err) } pluginruntime.Run(&MyPlugin{def: def})}3. Ship the console UI
Section titled “3. Ship the console UI”Plugins serve their own list and detail HTML from an embedded filesystem
mounted at /console/ by the runtime. Implement ConsoleProvider:
package main
import ( "embed" "net/http"
"github.com/fundament-oss/fundament/plugin-sdk/helpers/console")
//go:embed consolevar consoleFS embed.FS
func (p *MyPlugin) ConsoleAssets() http.FileSystem { return console.MustNewFileSystem(consoleFS)}Put one HTML file per customComponents entry under console/ (e.g.
console/myresources-list.html, console/myresources-detail.html).
See Custom UI for what those pages need to do and
Example: cert-manager for a worked layout.
4. Build a container image
Section titled “4. Build a container image”FROM golang:1.26-alpine AS builderWORKDIR /buildCOPY go.mod go.sum ./RUN go mod downloadCOPY . .RUN CGO_ENABLED=0 go build -o /bin/my-plugin ./plugins/my-plugin
FROM alpine:3.21# Add any CLI tools your plugin needs (e.g. helm)COPY --from=builder /bin/my-plugin /my-pluginCOPY plugins/my-plugin/definition.yaml /app/definition.yamlWORKDIR /appENTRYPOINT ["/my-plugin"]5. Create a PluginInstallation
Section titled “5. Create a PluginInstallation”apiVersion: plugins.fundament.io/v1kind: PluginInstallationmetadata: name: my-pluginspec: image: registry.example.com/my-plugin:v1.0.0 pluginName: my-plugin # Only if your plugin needs cluster-wide access: # clusterRoles: # - cluster-adminMetadata API
Section titled “Metadata API”Every plugin exposes a ConnectRPC service that the controller and console consume:
service PluginMetadataService { rpc GetStatus(GetStatusRequest) returns (GetStatusResponse); rpc GetDefinition(GetDefinitionRequest) returns (GetDefinitionResponse);}| Consumer | Method | Purpose |
|---|---|---|
| Plugin Controller | GetStatus | Poll phase, message, version → write to CR .status |
| Console Frontend | GetDefinition | Fetch menu entries, UI hints, CRDs → render plugin UI |
Plugin sandbox
Section titled “Plugin sandbox”A self-contained development environment for plugin development. See plugins/README.md for setup instructions and available commands.