How Z-CMS Loads Plugins Without Restarting the Server
A lifecycle-oriented approach to zero-downtime plugin activation and updates
Why restart-based plugin management does not scale
Restarting a server after every plugin installation may be acceptable for a personal website, but it becomes disruptive when a CMS serves many tenants, active sessions, background jobs and real-time connections. Z-CMS treats a plugin as a managed runtime that can be installed, activated, drained, replaced and removed independently.
Dynamic import is only the first step
Dynamic import can load code, but it does not provide safe unloading. Module caches remain, old event listeners can survive, cron jobs may continue and database connections may leak. Hot plugin management is therefore a lifecycle problem rather than a module-loading trick.
The plugin lifecycle
Downloaded
β
Verified
β
Installed
β
Initialized
β
Activated
β
Running
β
Deactivated
β
UninstalledUpdates introduce an additional sequence: download the new version, verify signatures, validate compatibility, check migrations, start a new runtime, run health checks, switch the active version and finally drain the old runtime.
Manifest-driven initialization
{
"name": "@z-cms/plugin-seo",
"version": "1.2.0",
"main": "dist/index.js",
"engines": { "z-cms": ">=1.0.0" },
"permissions": ["content:read", "content:update"],
"hooks": ["content.beforePublish", "content.afterPublish"]
}The manifest tells the Core which entry point to load, which CMS versions are supported, which permissions are required and which hooks must be registered.
Creating a new runtime
const runtime = await pluginRuntimeManager.create({
pluginId: plugin.id,
version: plugin.version,
memoryLimitMb: 64,
timeoutMs: 2_000,
});
await runtime.load(plugin.bundlePath);
await runtime.call("activate", { settings, permissions });Only after activation and a health check succeed is the runtime allowed to receive production events.
Hook registry instead of hard-coded calls
Hook Registry
β
βββ content.beforePublish
β βββ plugin-seo
β βββ plugin-moderation
βββ content.afterPublish
β βββ plugin-analytics
β βββ plugin-webhook
βββ media.afterUpload
βββ plugin-image-optimizerCore services dispatch an event to the registry. The registry resolves active plugins and routes execution to the correct isolate. Disabling a plugin removes its registrations immediately without restarting the application.
Managed resources and deactivation
Plugins can create listeners, schedules, subscriptions and temporary resources. The SDK therefore routes registration through managed APIs. Z-CMS records which resource belongs to which plugin and can clean it automatically during deactivation.
export default definePlugin({
async activate(cms) {
cms.events.on("content.published", handlePublished);
},
async deactivate(cms) {
cms.events.off("content.published", handlePublished);
},
});A well-written deactivate method is still encouraged, but Core cleanup does not rely exclusively on plugin discipline.
Blue-green plugin updates
plugin-seo
β
βββ Runtime A β 1.2.0 β Active
βββ Runtime B β 1.3.0 β StartingThe new version starts alongside the current version. It loads, activates and passes a smoke test before the registry pointer changes. If the health check fails, the old version remains active.
Draining active executions
The old runtime stops accepting new executions but continues handling work already in progress. An active execution counter and a maximum drain timeout prevent requests from being interrupted unnecessarily.
Accept new executions: No
Wait for active executions: Yes
Maximum drain timeout: 30 secondsMigrations and rollback
Plugin updates may include data migrations. Z-CMS records migration identifiers, creates a recovery point, runs compatibility checks and starts the new runtime before committing the switch. Reversible migrations can be rolled back automatically when activation fails.
{
"migrations": [{
"id": "20260714_add_focus_keyword",
"up": "migrations/001-up.js",
"down": "migrations/001-down.js"
}]
}Versioned storage
/plugins/plugin-seo/
βββ 1.2.0/bundle.js
βββ 1.3.0/bundle.js
βββ active.jsonKeeping previous verified bundles enables fast rollback and reproducible incident investigation.
Circuit breakers and development mode
A plugin that repeatedly fails can be moved from Active to Degraded and then Suspended. In development, a file watcher rebuilds the bundle, creates a new isolate, runs activation and swaps runtimes, giving developers a hot-reload experience without restarting the entire backend.
Conclusion
Loading plugins without a server restart requires a complete lifecycle: validation, runtime creation, registry switching, managed cleanup, draining, migration handling, rollback and health monitoring. Z-CMS treats plugin versions as replaceable runtime units so the CMS can remain available while its ecosystem evolves.
GitHub: https://github.com/zscontributor/z-cms
Website: https://z-cms.org