Plugin Development Guide
August 12, 2026 · View on GitHub
aflare supports loading community-contributed plugins as Go .so files. Plugins can add custom
nodes to the workflow engine or extend core functionality — without modifying the aflare source code.
Platform support: Go plugins (
-buildmode=plugin) are only available on Linux and macOS. Thepluginpackage is not supported on Windows. If you need cross-platform extensibility, consider using aflare's built-in MCP server support or contributing a built-in node instead.
Architecture
~/.config/aflare/plugins/
├── my-node-plugin.so # Go plugin: exports "Plugin" symbol
├── db-connector.so # Another custom node plugin
└── custom-auth.so # Extension plugin
At startup, aflare scans ~/.config/aflare/plugins/*.so, opens each shared object, and registers
the exported Plugin symbol with the PluginManager. Each plugin is then enabled (initialized)
after dependency checks pass.
Plugin Interface
Every plugin must implement the plugins.Plugin interface:
// internal/plugins/plugin.go
type PluginInfo struct {
Name string
Version string
Description string
Author string
Type string // "node" or "extension"
Dependencies []string
}
type Plugin interface {
GetInfo() PluginInfo
Init() error
Shutdown() error
}
For node-type plugins, also implement the NodePlugin interface:
type NodePlugin interface {
Plugin
GetNodes() []interface{} // returns node names this plugin provides
}
Writing a Plugin
Step 1: Create the plugin package
Create a new Go module for your plugin:
my-plugin/
├── go.mod
└── main.go
Step 2: Implement the Plugin interface
package main
import (
"github.com/alib8b8/aflare/internal/plugins"
"github.com/alib8b8/aflare/internal/core"
)
// Plugin is the exported symbol that aflare discovers.
// The name MUST be "Plugin" and its type MUST implement plugins.Plugin.
var Plugin = &MyNodePlugin{
info: plugins.PluginInfo{
Name: "my-custom-node",
Version: "1.0.0",
Description: "Adds a custom weather lookup node to aflare",
Author: "Your Name",
Type: plugins.PluginTypeNode,
Dependencies: []string{}, // optional: list plugin names this depends on
},
}
type MyNodePlugin struct {
info plugins.PluginInfo
reg *core.Registry
}
func (p *MyNodePlugin) GetInfo() plugins.PluginInfo {
return p.info
}
func (p *MyNodePlugin) Init() error {
// Register your custom nodes with the global registry.
// core.RegisterNode("weather_lookup", &WeatherNode{})
return nil
}
func (p *MyNodePlugin) Shutdown() error {
// Cleanup resources (close connections, flush buffers, etc.)
return nil
}
func (p *MyNodePlugin) GetNodes() []interface{} {
return []interface{}{"weather_lookup"}
}
Step 3: Build the .so file
cd my-plugin
go build -buildmode=plugin -o my-custom-node.so .
Step 4: Install
mkdir -p ~/.config/aflare/plugins
cp my-custom-node.so ~/.config/aflare/plugins/
Restart aflare. The plugin loads automatically at startup.
Plugin Lifecycle
sequenceDiagram
participant S as Startup
participant L as Loader
participant PM as PluginManager
participant P as Plugin
S->>L: Scan ~/.config/aflare/plugins/*.so
L->>L: plugin.Open(path)
L->>L: Lookup("Plugin")
L->>PM: Register(plugin)
PM->>PM: Validate name, type, no duplicates
L->>PM: Enable(name)
PM->>PM: Check dependencies
PM->>P: Init()
P-->>PM: nil
PM->>PM: Mark enabled
- Register — Plugin is added to the manager and validated.
- Enable — Dependencies are checked; if all are satisfied,
Init()is called. - Execute — Plugin nodes are available in workflows.
- Disable —
Shutdown()is called and the plugin is removed from the active set. - Unregister — Plugin is completely removed from the manager.
Dependency Management
If your plugin depends on another plugin, list it in PluginInfo.Dependencies:
var Plugin = &MyAdvancedPlugin{
info: plugins.PluginInfo{
Name: "my-advanced-plugin",
Type: plugins.PluginTypeNode,
Dependencies: []string{"my-custom-node"},
},
}
The dependency plugin must be:
- Registered — present in the plugin manager
- Enabled — already initialized before the dependent plugin
If a dependency is missing or disabled, Enable() returns an error.
Built-in Example Plugins
The internal/plugins/plugin.go file includes two reference implementations:
EchoPlugin
A simple node plugin that provides an echo_node:
func (e *EchoPlugin) GetNodes() []interface{} {
return []interface{}{"echo_node"}
}
ReversePlugin
A node plugin that depends on echo:
func NewReversePlugin() *ReversePlugin {
return &ReversePlugin{
info: PluginInfo{
Name: "reverse",
Type: PluginTypeNode,
Dependencies: []string{"echo"},
},
}
}
Startup Integration
In cmd/aflare/main.go, plugins are loaded during startup:
pluginMgr := plugins.NewPluginManager()
if n, err := plugins.LoadDir(plugins.DefaultPluginDir(), pluginMgr); err != nil {
log.Printf("[main] plugin loading: %v", err)
} else if n > 0 {
log.Printf("[main] loaded %d plugin(s)", n)
}
The default plugin directory is ~/.config/aflare/plugins/. Non-.so files and directories
are silently skipped.
Plugin Types
| Type | Description | When to use |
|---|---|---|
node | Adds new workflow nodes | Custom LLM providers, database connectors, API integrations |
extension | Extends core functionality | Custom authentication, logging, metrics |
Security Considerations
- Trust the source —
.soplugins run in-process with the same privileges as aflare. Only install plugins from trusted sources. - Review the code — Go plugins have full access to the process memory and file system.
- No sandbox — Plugins are not isolated; a buggy plugin can crash the entire process.
- Sensitive data — Do not hard-code API keys or credentials in plugin code; use aflare's configuration system instead.
Best Practices
- Version your plugins — Use semantic versioning in
PluginInfo.Version. - Keep
Init()lightweight — Heavy initialization delays startup; consider lazy loading. - Clean up in
Shutdown()— Close connections, stop goroutines, flush buffers. - Handle errors gracefully — Return meaningful errors from
Init()andShutdown(). - Name your export
Plugin— The loader looks for the symbol namedPluginwith the exactplugins.Plugininterface type. - Match the aflare build — Plugins must be compiled with the same Go version and dependency versions as the aflare binary they target.
Troubleshooting
Plugin not loading at startup
- Check the plugin file has
.soextension - Verify it's in
~/.config/aflare/plugins/ - Check the startup log for
[plugins]or[main]messages
"lookup Plugin symbol" error
The .so file must export a package-level variable named Plugin:
var Plugin plugins.Plugin = &MyPlugin{}
"does not implement plugins.Plugin" error
The exported symbol's type must match the plugins.Plugin interface exactly. Ensure both
your plugin and aflare are compiled with the same plugins package (same import path).
Build errors
Plugins must be built with the same Go toolchain version as aflare:
go version
go build -buildmode=plugin -o myplugin.so .
Windows: "plugin" package not available
Go's plugin package is not available on Windows. If you see plugin: not implemented on windows/amd64, you are running on an unsupported platform. Use Linux or macOS for plugin
development, or use aflare's MCP server support for cross-platform extensibility.
Current Status
aflare ships with 100+ built-in nodes covering most use cases. The plugin system is available for community extensions, but the built-in set is sufficient for most users. Consider contributing a plugin when:
- You need a custom LLM provider not yet supported
- You want to integrate with an internal/proprietary API
- You have a domain-specific node that benefits the broader community