Architecture
How mixin-style hook pipelines compose across plugins.
Mixin-style hook pipeline
FeatherFly plugins extend the daemon without recompiling it, similar to how Minecraft mixins inject behavior into existing code paths.
Daemon startup
│
├─ load .so files (alphabetical)
│ └─ init(host) ── register all hooks
│
├─ config.mutate pipeline ──► [plugin A] ──► [plugin B] ──► final YAML
│
├─ config.loaded (post-mutation YAML bytes)
│
└─ HTTP listening
│
├─ request.intercept ──► middleware.inject ──► handler
│
├─ plugin routes (route.register)
│
├─ CloudPanel API ──► cloudpanel.command ──► clpctl
│
└─ JSON response for /api/*
├─ json.response pipeline
└─ json.actions pipeline
How a JSON mixin chain works
- FeatherFly builds the base JSON response for a route.
- Each plugin registered for
json.responseon that route prefix runs in load order. - Plugin 1 receives the original JSON, may mutate it, writes output.
- Plugin 2 receives plugin 1's output, may mutate again.
- After all body hooks,
json.actionshooks run on theactionsarray only. - The final JSON is sent to the client.
This is intentionally composable: small plugins each do one job (add a field, inject a step, strip sensitive data) instead of one monolithic plugin patching everything.
Lifecycle vs mutation
| Hook type | When it runs | Can cancel others? | Typical use |
|---|---|---|---|
| Lifecycle event | Fixed daemon milestones | Yes (same event only) | Startup banners, config validation |
| JSON mutation | Every matching HTTP JSON response | No — always runs in pipeline | Response fields, panel actions |
| CloudPanel command | Before clpctl process spawn | Yes — cancels command execution | Policy checks, defaults |
Versioning
Plugin API v8 (current) exposes lifecycle events, config mutation, request hooks, plugin routes, JSON mutation, and CloudPanel command hooks. Future hooks will bump the API version so old plugins keep loading safely when new hook types appear.
HTTP request stack
HTTP request stack
Client request
│
▼
response_middleware (pass-through on request; mutates JSON on response)
│
▼
request.intercept hooks (outer — auth, rate limits, early reject)
│
▼
middleware.inject hooks (inner — tracing, header checks)
│
▼
handle_request (debug logging)
│
▼
Axum router (core routes + plugin routes)
Request hooks receive method, path, headers as JSON, and body bytes. Headers use lowercase keys. authorization is redacted.
Route patterns use prefix matching: /api/* matches /api/system, /plugins/hello matches exactly that path prefix chain.
Plugin routes register during init and merge into the same router as core API routes.
See also request hooks, config hooks, and hooks roadmap.