Managing Plugins
Managing Plugins
This guide covers how to install, configure, approve, monitor, and troubleshoot plugins as a CMS administrator. For building plugins, see the Plugin Developer Documentation.
What Plugins Do
Plugins extend ModulaCMS with custom functionality provided by third-party developers or your own team. A plugin can:
- Add custom REST API endpoints under
/api/v1/plugins/<plugin_name>/ - React to content lifecycle events (create, update, delete, publish, archive)
- Store its own data in isolated database tables
Plugins run in sandboxed environments and cannot access the filesystem, execute system commands, or read core CMS database tables. Each plugin's data is isolated from other plugins and from the CMS itself.
Enabling the Plugin System
The plugin system is disabled by default. Enable it in modula.config.json:
{
"plugin_enabled": true,
"plugin_directory": "./plugins/"
}
plugin_directory is the path where ModulaCMS scans for plugin directories. Each subdirectory containing an init.lua file is treated as a plugin.
Restart the server after changing plugin_enabled.
Installing a Plugin
Copy the plugin's directory into your plugin_directory:
plugins/
task_tracker/
init.lua
lib/
helpers.lua
The server picks up new plugins on startup. If plugin_hot_reload is enabled, the server detects new plugins automatically without a restart.
Validating Before Installation
Check that a plugin is well-formed before deploying it:
modula plugin validate ./plugins/task_tracker
This checks the manifest (plugin_info table in init.lua), file structure, and basic syntax without loading the plugin into the server.
Configuration
Essential Settings
| Field | Type | Default | Description |
|---|---|---|---|
plugin_enabled |
bool | false | Master switch for the plugin system |
plugin_directory |
string | "./plugins/" |
Path to scan for plugin directories |
plugin_hot_reload |
bool | false | Auto-reload plugins when Lua files change |
Resource Limits
These settings protect the CMS from misbehaving plugins:
| Field | Type | Default | Description |
|---|---|---|---|
plugin_max_vms |
int | 4 | VM pool size per plugin |
plugin_timeout |
int | 5 | Execution timeout per handler (seconds) |
plugin_max_ops |
int | 1000 | Max database operations per request |
plugin_rate_limit |
int | 100 | Max requests per second per IP |
plugin_max_routes |
int | 50 | Max HTTP routes per plugin |
plugin_max_request_body |
int | 1048576 | Max request body size (bytes, default 1 MB) |
plugin_max_response_body |
int | 5242880 | Max response body size (bytes, default 5 MB) |
Circuit Breaker Settings
| Field | Type | Default | Description |
|---|---|---|---|
plugin_max_failures |
int | 5 | Consecutive failures before the plugin is disabled |
plugin_reset_interval |
string | "60s" |
Wait time before retrying a disabled plugin |
plugin_hook_max_consecutive_aborts |
int | 10 | Consecutive hook errors before that hook is disabled |
Hook Settings
| Field | Type | Default | Description |
|---|---|---|---|
plugin_hook_reserve_vms |
int | 1 | VMs reserved for hook execution |
plugin_hook_max_ops |
int | 100 | Max database operations per hook |
plugin_hook_max_concurrent_after |
int | 10 | Max concurrent after-hook goroutines |
plugin_hook_timeout_ms |
int | 2000 | Per-hook execution timeout (ms) |
plugin_hook_event_timeout_ms |
int | 5000 | Total timeout for all hooks on one event (ms) |
Proxy Settings
| Field | Type | Default | Description |
|---|---|---|---|
plugin_trusted_proxies |
[]string | [] | IP ranges trusted for X-Forwarded-For parsing |
Route and Hook Approval
Plugins register HTTP routes and content hooks, but none of them are active until an admin approves them. This is a security gate -- new or updated plugin code does not execute until explicitly allowed.
When a plugin's version changes (in its plugin_info.version field), all existing approvals are revoked and must be re-approved.
Viewing Pending Approvals
# List all routes with approval status
curl http://localhost:8080/api/v1/admin/plugins/routes \
-H "Cookie: session=YOUR_SESSION_COOKIE"
# List all hooks with approval status
curl http://localhost:8080/api/v1/admin/plugins/hooks \
-H "Cookie: session=YOUR_SESSION_COOKIE"
Approving via CLI
# Approve all routes and hooks for a plugin
modula plugin approve task_tracker --all-routes
modula plugin approve task_tracker --all-hooks
# Approve specific items
modula plugin approve task_tracker --route "GET /tasks"
modula plugin approve task_tracker --hook "before_create:content_data"
# Skip confirmation prompts (for CI/CD)
modula plugin approve task_tracker --all-routes --all-hooks --yes
Approving via API
# Approve routes
curl -X POST http://localhost:8080/api/v1/admin/plugins/routes/approve \
-H "Cookie: session=YOUR_SESSION_COOKIE" \
-H "Content-Type: application/json" \
-d '{
"routes": [
{"plugin": "task_tracker", "method": "GET", "path": "/tasks"},
{"plugin": "task_tracker", "method": "POST", "path": "/tasks"}
]
}'
# Approve hooks
curl -X POST http://localhost:8080/api/v1/admin/plugins/hooks/approve \
-H "Cookie: session=YOUR_SESSION_COOKIE" \
-H "Content-Type: application/json" \
-d '{
"hooks": [
{"plugin": "task_tracker", "event": "after_create", "table": "content_data"}
]
}'
Approving via TUI
Navigate to the Plugins page from the homepage menu. Select a plugin to view its routes and hooks, then approve through the confirmation dialog.
Revoking Approvals
modula plugin revoke task_tracker --all-routes
modula plugin revoke task_tracker --route "GET /tasks"
Monitoring
Plugin Status
modula plugin info task_tracker
This shows:
- Current lifecycle state (Discovered, Loading, Running, Failed, Stopped)
- Circuit breaker state (Closed, Open, Half-Open)
- VM pool utilization
- Route and hook counts with approval status
- Schema drift warnings (if table definitions don't match actual database columns)
Via API
# List all plugins with state
curl http://localhost:8080/api/v1/admin/plugins \
-H "Cookie: session=YOUR_SESSION_COOKIE"
# Detailed info for one plugin
curl http://localhost:8080/api/v1/admin/plugins/task_tracker \
-H "Cookie: session=YOUR_SESSION_COOKIE"
Listing Plugins
modula plugin list
Lifecycle Management
Enable / Disable
modula plugin enable task_tracker # reset circuit breaker, reload
modula plugin disable task_tracker # stop plugin, trip circuit breaker
Disabling a plugin immediately stops it from serving traffic. Enabling resets the circuit breaker and reloads the plugin.
Reload
modula plugin reload task_tracker
Triggers a blue-green reload: a new instance is created alongside the old one. If it loads successfully, it replaces the old instance atomically. If it fails, the old instance keeps running.
Hot Reload
When plugin_hot_reload is true, the server watches for Lua file changes every 2 seconds and reloads automatically. Safety limits prevent reload storms:
- 1-second debounce window
- 10-second cooldown between reloads per plugin
- After 3 consecutive slow reloads (>10s each), auto-reload pauses for that plugin
Hot reload is intended for development. Disable it in production.
Circuit Breaker
The circuit breaker protects the CMS from failing plugins.
Plugin-level: After consecutive failures (default 5), the circuit breaker opens and all requests to the plugin return HTTP 503. After the reset interval (default 60s), a single probe request is allowed. If it succeeds, the breaker closes. If it fails, it re-opens.
| State | Behavior |
|---|---|
| Closed | Normal operation |
| Open | All requests return 503 |
| Half-Open | One probe request allowed |
Hook-level: Each (plugin, event, table) combination has its own breaker. After 10 consecutive errors (configurable), that specific hook is disabled. Hook failures do not affect the plugin-level circuit breaker.
Reset the circuit breaker manually:
modula plugin enable task_tracker
Cleanup
Over time, plugins may leave behind orphaned database tables (e.g., after a plugin is removed but its tables remain). The cleanup endpoints handle this.
# Dry run: list orphaned tables without deleting
curl http://localhost:8080/api/v1/admin/plugins/cleanup \
-H "Cookie: session=YOUR_SESSION_COOKIE"
# Drop orphaned tables (requires confirmation)
curl -X POST http://localhost:8080/api/v1/admin/plugins/cleanup \
-H "Cookie: session=YOUR_SESSION_COOKIE" \
-H "Content-Type: application/json" \
-d '{"tables": ["plugin_old_plugin_tasks", "plugin_old_plugin_categories"]}'
Troubleshooting
Plugin shows "Failed" state: Check the server logs for the on_init() error. Common causes: missing table definition, invalid SQL, dependency plugin not loaded.
Routes return 404: Routes must be approved before they serve traffic. Check modula plugin info <name> for unapproved routes.
Hooks not firing: Hooks must be approved. Check hook approval status. Also verify the event and table name match what the plugin registered.
Circuit breaker tripped: Check logs for the error pattern. Fix the underlying issue, then modula plugin enable <name> to reset.
Schema drift warnings: The plugin's table definition doesn't match the actual database columns. This is advisory -- the plugin still works, but the developer should update the definition or migrate the table.
Rate limiting (429 responses): Increase plugin_rate_limit or investigate why the client is sending too many requests.
Admin API Reference
All endpoints require authentication. Admin endpoints require the plugins:admin permission; read endpoints require plugins:read.
| Method | Path | Permission | Description |
|---|---|---|---|
| GET | /api/v1/admin/plugins |
plugins:read |
List all plugins with state |
| GET | /api/v1/admin/plugins/{name} |
plugins:read |
Plugin details and circuit breaker state |
| POST | /api/v1/admin/plugins/{name}/reload |
plugins:admin |
Trigger hot reload |
| POST | /api/v1/admin/plugins/{name}/enable |
plugins:admin |
Reset circuit breaker, reload |
| POST | /api/v1/admin/plugins/{name}/disable |
plugins:admin |
Stop plugin |
| GET | /api/v1/admin/plugins/cleanup |
plugins:admin |
List orphaned tables (dry run) |
| POST | /api/v1/admin/plugins/cleanup |
plugins:admin |
Drop orphaned tables |
| GET | /api/v1/admin/plugins/routes |
plugins:read |
List routes with approval status |
| POST | /api/v1/admin/plugins/routes/approve |
plugins:admin |
Approve routes |
| POST | /api/v1/admin/plugins/routes/revoke |
plugins:admin |
Revoke route approvals |
| GET | /api/v1/admin/plugins/hooks |
plugins:read |
List hooks with approval status |
| POST | /api/v1/admin/plugins/hooks/approve |
plugins:admin |
Approve hooks |
| POST | /api/v1/admin/plugins/hooks/revoke |
plugins:admin |
Revoke hook approvals |
CLI Reference
| Command | Server Required | Description |
|---|---|---|
modula plugin list |
No | List discovered plugins |
modula plugin validate <path> |
No | Validate a plugin without loading |
modula plugin info <name> |
Yes | Plugin status, circuit breaker, approvals |
modula plugin reload <name> |
Yes | Blue-green hot reload |
modula plugin enable <name> |
Yes | Reset circuit breaker, reload |
modula plugin disable <name> |
Yes | Stop plugin |
modula plugin approve <name> [flags] |
Yes | Approve routes/hooks |
modula plugin revoke <name> [flags] |
Yes | Revoke approvals |