Build powerful server management tools in minutes — no native code required.
AXPluginKit is the official starter kit for building plugins on AevonX, the macOS server management platform. Write a .json file, drop it on your server, and your plugin appears instantly inside AevonX. When you're ready for more, graduate to a native binary plugin with a full configuration UI, lifecycle scripts, and a systemd-managed daemon.
AevonX plugins can do anything you need inside the app:
| Capability | Component |
|---|---|
| Action buttons on website, database, or server cards | "button" |
| Live data tables with auto-refresh, sorting, search, and filters | "data_table" |
| Full sidebar pages with dashboards, charts, and cards | "page" |
| Forms that collect input before running a command | "form" |
| Stats cards that show key metrics at a glance | "stats_card" |
| Modals that pop up with detailed information | "modal" |
| Area, line, bar, pie charts from any data source | "chart" |
| Streaming log viewers with live tail output | streaming_cmd |
Plugins inject into 40+ hook points across the app — website cards, database cards, server overview, sidebar tabs, global toolbar, and more. You don't write any Swift or Go — the platform handles all rendering, caching, permissions, and security automatically.
Drop .json files into /etc/aevonx/your-tool/ on any server. No build step, no binary. Best for UI extensions, dashboards, and action buttons that call built-in actions.
Publish a Go binary alongside a config.avx schema, setup.sh, uninstall.sh, and a _manifest.json that declares your own custom actions. AevonX installs it as a systemd service, exposes a configuration UI from your schema, and routes action calls to your binary. Best for tools that need their own data, APIs, or background processing.
AevonX scans /etc/aevonx/ on every server connection. Each subdirectory is your plugin's namespace:
/etc/aevonx/
my-tool/ ← your namespace
_manifest.json ← plugin metadata (optional for JSON-only)
traffic-table.json ← plugin #1
backup-button.json ← plugin #2
dashboard.json ← plugin #3
No registration, no API keys, no build step. AevonX discovers and renders your plugins automatically.
ssh user@your-serversudo mkdir -p /etc/aevonx/my-first-pluginsudo nano /etc/aevonx/my-first-plugin/backup-button.jsonPaste this:
{
"id": "my-first-plugin-backup",
"name": "Quick Backup",
"description": "One-click website backup from any website card.",
"hook": "remote_fleets.websites.card.actions",
"component": "button",
"icon": "externaldrive.badge.plus",
"style": "primary",
"label": "Backup Now",
"version": "1.0.0",
"enabled": true,
"confirmation_message": "Create a backup of {{website.name}}?",
"command": {
"type": "core_cmd",
"action": "website.backup",
"payload": { "website_id": "{{website.id}}" },
"on_success": "Backup started for {{website.name}}",
"on_error": "Backup failed. Please try again."
}
}Connect to the server. Your plugin appears automatically on every website card.
Reload plugins anytime from the Plugins tab — no reconnection needed.
Seven progressively complex examples. Start from #1 and work your way up.
01 — Hello Button Beginner
A single action button on every website card. Confirms before triggering a backup.
Teaches: button component · command · template variables · confirmation dialogs · button styles
02 — Live Traffic Table Beginner
A sidebar tab showing real-time Nginx access logs as a sortable, searchable table. Auto-refreshes every 30 seconds.
Teaches: data_table · data_source · data formats (csv, nginx) · column types (ip, url, status, bytes, datetime) · refresh_interval
03 — Server Dashboard Intermediate
Two plugins working together: a full dashboard page with metric cards, and a services table — both in the sidebar.
Teaches: page component · layout.type: "dashboard" · multiple plugins per namespace · json format with rows_path
04 — Form Action Intermediate
Two card-action plugins: a form that collects input before cloning a website, and a button that force-renews SSL. Both use async_cmd for long-running operations.
Teaches: form component · auto-generated form fields from {{payload_keys}} · async_cmd vs core_cmd · conditions · timeout
05 — Advanced Multi-Component Advanced
A complete "Security Auditor" with four components: sidebar dashboard, failed-logins table, open-ports widget, and a conditional audit button.
Teaches: Multi-component namespaces · overview.widgets hook · servers.card.actions · conditions with operators · streaming_cmd · allowed_actions
06 — Native Binary Plugin Advanced
A complete native plugin — "ServerPulse" — with a Go binary, config.avx configuration schema, setup.sh, uninstall.sh, Makefile, and three hook definitions.
Teaches: config.avx schema · setup.sh / uninstall.sh pattern · full _manifest.json · allowed_actions · lifecycle · health_check · row_actions · filters · batch_actions
07 — Advanced Table Patterns Advanced
Four plugins demonstrating every advanced table feature: per-row actions, multi-select batch actions, column filters, area chart, and streaming log viewer.
Teaches: row_actions · batch_actions · filters with operators · chart component with chart_config · streaming_cmd · {{selected.field}} batch variables
{
"id": "my-namespace-plugin-name",
"name": "Display Name",
"description": "What it does — shown in the Plugins tab.",
"hook": "remote_fleets.sidebar.tabs",
"component": "data_table",
"icon": "chart.bar",
"version": "1.0.0",
"enabled": true
}Hooks define where your plugin appears.
| Hook | Location |
|---|---|
remote_fleets.sidebar.tabs |
Left sidebar — new tab |
remote_fleets.global.header.actions |
Top-right global toolbar |
remote_fleets.global.toolbar |
Main toolbar |
remote_fleets.global.status_bar |
Status bar at the bottom |
| Hook | Location |
|---|---|
remote_fleets.overview.widgets |
Overview widget area |
remote_fleets.overview.header |
Overview tab header |
remote_fleets.overview.stats |
Stats row |
remote_fleets.overview.charts |
Charts section |
remote_fleets.overview.alerts |
Alerts section |
| Hook | Location |
|---|---|
remote_fleets.websites.card.actions |
Action buttons on each website card |
remote_fleets.websites.details.header |
Website detail page header |
remote_fleets.websites.details.tabs |
Tabs inside website detail |
remote_fleets.websites.details.sidebar |
Right sidebar in website detail |
remote_fleets.websites.list.toolbar |
Toolbar above the website list |
remote_fleets.websites.nginx.actions |
Nginx section actions |
| Hook | Location |
|---|---|
remote_fleets.databases.card.actions |
Action buttons on each database card |
remote_fleets.databases.details.actions |
Database detail actions |
remote_fleets.databases.details.tabs |
Tabs inside database detail |
remote_fleets.databases.list.toolbar |
Toolbar above the database list |
| Hook | Location |
|---|---|
remote_fleets.servers.card.actions |
Server list card actions |
| Hook | Location |
|---|---|
remote_fleets.firewall.actions |
Firewall section actions |
remote_fleets.security.firewall.rules |
Firewall rules view |
remote_fleets.security.ssl.actions |
SSL certificates actions |
remote_fleets.security.audit.widgets |
Security audit widget area |
| Hook | Location |
|---|---|
remote_fleets.docker.container.actions |
Docker container actions |
remote_fleets.docker.toolbar |
Docker section toolbar |
remote_fleets.docker.stats.widgets |
Docker stats widget area |
| Hook | Location |
|---|---|
remote_fleets.monitoring.widgets |
Monitoring tab widgets |
remote_fleets.dns.actions |
DNS management actions |
remote_fleets.cron.actions |
Cron jobs actions |
remote_fleets.backups.actions |
Backups section actions |
remote_fleets.users.actions |
Server users actions |
remote_fleets.plugins.marketplace |
Plugin marketplace view |
remote_fleets.plugins.settings |
Plugin settings view |
{
"component": "button",
"label": "Run Action",
"icon": "play.fill",
"style": "primary",
"confirmation_message": "Are you sure?",
"command": { ... }
}Styles: primary · secondary · danger · warning · success · ghost
{
"component": "data_table",
"data_source": {
"action": "monitoring.nginx.access_stats",
"format": "csv",
"refresh_interval": 30,
"rows_path": "data.rows"
},
"columns": [
{ "key": "ip", "label": "IP", "type": "ip", "width": 140, "sortable": true }
]
}Data formats:
| Format | Use when |
|---|---|
json |
Response is a JSON array (or nested via rows_path) |
csv |
First line = headers, rest = rows |
tsv |
Tab-separated, same rules as CSV |
lines |
One value per line → { "value": "..." } |
key_value |
KEY=VALUE or KEY: VALUE pairs |
nginx |
Nginx combined log — auto-parses ip, method, path, status, bytes, date |
apache |
Apache combined log format |
raw |
Raw text, no parsing |
Column types:
| Type | Renders as |
|---|---|
text |
Plain text |
number |
Formatted number: 1,234,567 |
bytes |
Auto-scaled: 1.2 MB, 3.4 GB |
percent |
Progress bar + percentage |
status |
Colored dot: active = green, error = red |
badge |
Blue pill badge |
url |
Underlined blue link |
ip |
Monospaced IP address |
boolean |
✓ or ✗ icon |
datetime |
Formatted date and time |
{
"component": "page",
"layout": {
"type": "dashboard",
"title": "My Page",
"columns": 2,
"cards": [
{
"title": "System Metrics",
"icon": "cpu",
"command": { "type": "core_cmd", "action": "monitoring.metrics" }
}
]
}
}Layout types: dashboard · overview · cards_details · table · grid · split
{
"component": "chart",
"chart_type": "area",
"chart_config": {
"x_key": "hour",
"y_key": "requests",
"color": "#3B82F6",
"fill_opacity": 0.2,
"show_grid": true,
"smooth": true
},
"data_source": { ... }
}Chart types: line · bar · area · pie · donut
{
"component": "form",
"label": "Clone Site",
"command": {
"type": "async_cmd",
"action": "website.clone",
"payload": {
"website_id": "{{website.id}}",
"destination_domain": "{{destination_domain}}"
}
}
}Payload keys in {{double_braces}} that don't match a context variable become form fields automatically.
{ "component": "stats_card", "command": { "type": "core_cmd", "action": "monitoring.metrics" } }
{ "component": "modal", "label": "View Details", "command": { "type": "core_cmd", "action": "website.status" } }{
"command": {
"type": "core_cmd",
"action": "website.backup",
"payload": { "website_id": "{{website.id}}" },
"timeout": 30,
"retries": 2,
"on_success": "Backup completed for {{website.name}}",
"on_error": "Backup failed. Please try again."
}
}| Type | Use when |
|---|---|
core_cmd |
Fast, synchronous operations |
async_cmd |
Long-running background operations (backup, clone, audit) |
streaming_cmd |
Live output — logs, builds, streaming processes |
Row actions appear in a context menu on each table row. Batch actions appear when two or more rows are selected.
{
"row_actions": [
{
"id": "delete-row",
"label": "Delete",
"icon": "trash",
"style": "danger",
"confirmation_message": "Delete {{name}}?",
"command": {
"type": "async_cmd",
"action": "your-plugin.delete",
"payload": { "id": "{{id}}" }
}
}
],
"batch_actions": [
{
"id": "delete-selected",
"label": "Delete Selected",
"icon": "trash",
"style": "danger",
"confirmation_message": "Delete {{count}} items?",
"command": {
"type": "async_cmd",
"action": "your-plugin.delete_batch",
"payload": { "ids": "{{selected.id}}" }
}
}
]
}{{count}} — number of selected rows
{{selected.field}} — array of that field from all selected rows
Column filters render above the table and narrow results client-side:
{
"filters": [
{
"key": "status",
"label": "Status",
"type": "select",
"options": ["active", "inactive", "error"]
},
{
"key": "size_bytes",
"label": "Larger than (MB)",
"type": "number",
"operator": "gt",
"transform": "mb_to_bytes"
},
{
"key": "name",
"label": "Name contains",
"type": "text",
"operator": "contains"
}
]
}Filter types: text · number · select · boolean
Operators: eq · neq · contains · gt · lt · exists
Actions are whitelisted for security. Only use actions from this list in command.action or data_source.action:
Websites
website.backup · website.ssl.renew · website.clone · website.status · website.restart · website.logs · website.access_stats
Databases
database.optimize · database.backup · database.size
Monitoring
monitoring.metrics · monitoring.processes · monitoring.disk.usage · monitoring.network.stats · monitoring.nginx.access_stats
Security
firewall.status · security.fail2ban.status · security.audit · security.scan · security.open_ports · security.failed_logins
System
system.info · system.services · system.cron · system.env · system.users
Need an action that isn't listed? Open a discussion. For native plugins, you define your own actions in
_manifest.jsonunderallowed_actions.
| Hook context | Variables |
|---|---|
websites.card.actions |
{{website.id}} · {{website.name}} · {{website.domain}} |
databases.card.actions |
{{database.name}} · {{database.engine}} |
servers.card.actions |
{{server.id}} · {{server.name}} · {{server.host}} |
sidebar.tabs |
{{server.id}} · {{server.name}} · {{server.host}} |
overview.widgets |
{{server.id}} · {{server.name}} |
| Row actions | Any column key from the row: {{ip}}, {{name}}, {{id}}, etc. |
Show a plugin only when a condition is true:
{
"conditions": [
{ "field": "website.status", "operator": "eq", "value": "active" }
]
}| Operator | Meaning |
|---|---|
eq |
Equals |
neq |
Not equals |
contains |
String contains |
gt |
Greater than |
lt |
Less than |
exists |
Field exists and is not null |
Multiple conditions are AND-ed together.
A _manifest.json gives your namespace a name and icon in the Plugins tab.
JSON-only plugin (minimal):
{
"name": "My Tool",
"description": "What this tool does",
"author": "Your Name",
"version": "1.0.0",
"icon": "puzzlepiece.fill",
"website": "https://github.com/your-username/my-tool"
}Native binary plugin (full):
{
"name": "My Tool",
"slug": "my-tool",
"description": "...",
"version": "1.0.0",
"icon": "puzzlepiece.fill",
"binary": "/usr/local/bin/my-tool exec",
"arg_style": "positional",
"service_name": "my-tool",
"config_dir": "/etc/aevonx/plugins/my-tool",
"hooks_dir": "/etc/aevonx/hooks/my-tool",
"allowed_actions": [
"my-tool.data.list",
"my-tool.action.run"
],
"health_check": {
"type": "systemd",
"service_name": "my-tool",
"interval": 30,
"success_pattern": "active"
},
"lifecycle": {
"directories": [
"/var/lib/my-tool:0755",
"/var/log/my-tool:0755"
],
"systemd": {
"description": "My Tool daemon",
"exec_args": "daemon --config /etc/aevonx/plugins/my-tool/config.avx",
"user": "nobody",
"restart_sec": 5,
"capabilities": []
}
}
}The full template with all fields is at templates/_manifest.full.json.
Native plugins expose a configuration UI in AevonX automatically — define the schema in config.avx and AevonX renders it.
{
"project": "MyTool",
"description": "...",
"version": "1.0.0",
"config_info": {
"color": "#3B82F6",
"accent_color": "#60A5FA",
"website_url": "https://..."
},
"config_schema": [
{
"section": "General",
"description": "Core settings.",
"fields": [
{ "key": "api_key", "label": "API Key", "type": "password", "value": "" },
{ "key": "timeout", "label": "Timeout (s)", "type": "number", "value": 30 },
{ "key": "log_level", "label": "Log Level", "type": "select", "value": "info", "options": ["debug","info","warn","error"] },
{ "key": "enabled", "label": "Enabled", "type": "boolean", "value": true }
]
},
{
"section": "Actions",
"fields": [
{
"key": "test",
"label": "Test Connection",
"type": "action",
"value": "my-tool test",
"action_style": "primary",
"description": "Verify the plugin can reach its endpoint."
},
{
"key": "reset",
"label": "Reset to Defaults",
"type": "action",
"value": "my-tool reset",
"action_style": "danger",
"confirm_message": "Reset all settings?"
}
]
}
]
}Field types:
| Type | UI |
|---|---|
string |
Single-line text input |
text |
Multi-line text area |
password |
Masked input, stored encrypted |
number |
Numeric input |
boolean |
Toggle switch |
select |
Dropdown picker (use options array) |
action |
Button that runs value as a shell command |
The full template is at templates/config.avx.
For native plugins, AevonX calls two scripts that you write:
Runs once after AevonX places your binary and creates the systemd service. Use it to:
- Create runtime directories your binary needs
- Check and install system dependencies
- Generate secrets and tokens
- Find a free port and open it in the firewall
- Configure log rotation
- Run any first-boot initialization
#!/usr/bin/env bash
set -euo pipefail
PLUGIN_DIR="/etc/aevonx/plugins/my-tool"
DATA_DIR="/var/lib/my-tool"
mkdir -p "$DATA_DIR"
# Generate a token only on first install
if [ ! -f "$PLUGIN_DIR/.token" ]; then
openssl rand -hex 32 > "$PLUGIN_DIR/.token"
chmod 600 "$PLUGIN_DIR/.token"
fi
# Find a free port
PORT=9460
while ss -ltn | grep -q ":$PORT "; do PORT=$((PORT+1)); done
echo "$PORT" > "$PLUGIN_DIR/.port"
# Open in firewall (loopback only)
ufw allow in on lo to any port "$PORT" proto tcp 2>/dev/null || trueRuns before AevonX stops the service and removes the binary. Undo every change setup.sh made, in reverse order.
#!/usr/bin/env bash
set -euo pipefail
PLUGIN_DIR="/etc/aevonx/plugins/my-tool"
# Close firewall port
PORT=$(cat "$PLUGIN_DIR/.port" 2>/dev/null || echo "")
[ -n "$PORT" ] && ufw delete allow in on lo to any port "$PORT" proto tcp 2>/dev/null || true
# Remove logrotate
rm -f /etc/logrotate.d/my-tool
# Remove runtime data
rm -rf /var/lib/my-tool /var/log/my-toolRule: AevonX removes: binary, config dir, hooks dir, systemd unit. You remove: everything else your
setup.shcreated.
The full annotated templates are at templates/setup.sh and templates/uninstall.sh.
BINARY_NAME = my-tool
BUILD_DIR = dist/build
VERSION = 1.0.0
all: build-amd64 build-arm64 package
build-amd64:
mkdir -p $(BUILD_DIR)
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 \
go build -trimpath -ldflags="-s -w -X main.Version=$(VERSION)" \
-o $(BUILD_DIR)/$(BINARY_NAME)-linux-amd64 ./cmd/$(BINARY_NAME)
build-arm64:
mkdir -p $(BUILD_DIR)
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 \
go build -trimpath -ldflags="-s -w -X main.Version=$(VERSION)" \
-o $(BUILD_DIR)/$(BINARY_NAME)-linux-arm64 ./cmd/$(BINARY_NAME)
package:
cd dist && zip -r build/$(BINARY_NAME)-$(VERSION).zip \
config.avx setup.sh uninstall.sh hooks/ build/$(BINARY_NAME)-linux-*AevonX detects the server's architecture (amd64 or arm64) during install and picks the correct binary automatically.
sudo mkdir -p /etc/aevonx/my-tool
sudo cp _manifest.json /etc/aevonx/my-tool/
sudo cp *.json /etc/aevonx/my-tool/
# Open AevonX → connect → plugin loads automaticallyUpload via the AevonX Marketplace tab, or install a dev build directly from a ZIP:
dist/
config.avx ← configuration schema
setup.sh ← post-install script
uninstall.sh ← pre-removal script
hooks/
_manifest.json ← full manifest with lifecycle + allowed_actions
*.json ← hook definitions
build/
my-tool-linux-amd64
my-tool-linux-arm64
my-tool-1.0.0.zip ← what you upload
Reload JSON-only plugins anytime from the Plugins tab without reconnecting.
| Template | Use for |
|---|---|
| _manifest.template.json | Minimal namespace manifest |
| _manifest.full.json | Full native plugin manifest with all fields |
| button.template.json | Action button |
| data_table.template.json | Basic data table |
| data_table_advanced.template.json | Table with row actions, batch actions, filters |
| page.template.json | Full sidebar page |
| form.template.json | Form with input collection |
| chart.template.json | Chart (area, line, bar, pie, donut) |
| config.avx | Native plugin configuration schema |
| setup.sh | Post-install lifecycle script |
| uninstall.sh | Pre-removal lifecycle script |
Remove
_comment_*keys before deploying — they are documentation only.
AXPluginKit/
├── README.md
├── templates/
│ ├── _manifest.template.json ← minimal manifest
│ ├── _manifest.full.json ← full native plugin manifest
│ ├── button.template.json
│ ├── data_table.template.json
│ ├── data_table_advanced.template.json ← row actions, batch, filters
│ ├── page.template.json
│ ├── form.template.json
│ ├── chart.template.json
│ ├── config.avx ← configuration schema
│ ├── setup.sh ← post-install script
│ └── uninstall.sh ← pre-removal script
└── examples/
├── 01-hello-button/ ← Beginner: action button
├── 02-live-traffic-table/ ← Beginner: live data table
├── 03-server-dashboard/ ← Intermediate: dashboard page
├── 04-form-action/ ← Intermediate: forms + async
├── 05-advanced-multi-component/ ← Advanced: full plugin suite
├── 06-native-plugin/ ← Advanced: binary + full lifecycle
│ └── dist/
│ ├── config.avx
│ ├── setup.sh
│ ├── uninstall.sh
│ ├── Makefile
│ └── hooks/
│ ├── _manifest.json
│ ├── overview-widget.json
│ ├── history-table.json
│ └── top-processes.json
└── 07-advanced-table/ ← Advanced: row actions, filters, chart, streaming
├── fail2ban-table.json
├── traffic-chart.json
├── live-log-stream.json
└── db-size-table.json
AevonX handles all security automatically — you focus on building your plugin.
- Actions are whitelisted. Only actions in
allowed_actions(or the platform whitelist) are executed. Everything else is rejected before reaching your binary. - Commands run as a limited user. Your binary runs as
nobodyby default unless you request specific Linux capabilities. - Lifecycle scripts run as root during install/uninstall only — your daemon runs as the user you specify.
- Rate limiting is enforced automatically per plugin per server.
- Configuration is stored encrypted on the server — passwords and tokens are never stored in plaintext.
One file, one purpose. Name files by their hook location: sidebar-dashboard.json, website-backup.json, server-audit.json.
Use async_cmd for anything slow. Anything taking more than a second should use async_cmd — AevonX shows a spinner and notifies the user on completion.
Use conditions to reduce noise. A button that appears only when relevant feels native, not bolted on.
Test with refresh_interval: 5. When building a data table, use a short interval to verify your data, then increase it for production.
Mirror setup.sh in uninstall.sh. Every port opened, every directory created, every logrotate file written — undo it in reverse order. A clean uninstall is a mark of quality.
Use descriptive IDs. Format: namespace-component-purpose — e.g., my-tool-sidebar-traffic, my-tool-website-backup.
Do I need to write any code? No — for JSON-only plugins. Native plugins require a Go binary, but you only need to write the parts specific to your tool. AevonX handles all rendering, caching, and security.
Can I use my own backend or external API? Native plugins can call any external API from their binary. JSON-only plugins communicate only through the built-in action whitelist.
How do I update a plugin?
For JSON-only plugins: edit the file and click reload. For native plugins: upload a new ZIP — AevonX preserves your config.avx settings across updates.
How do I share my plugin?
Publish as a GitHub repository tagged aevonx-plugin. Submit to the AevonX Marketplace to reach all users.
Where do I find SF Symbols icon names? Use the SF Symbols app (free, Mac only) to browse and search 6000+ icons.
MIT — free to use as the basis for your own plugins.
Built for AevonX — the macOS server management platform.