Plugin Development Overview
WNCMS supports project-level plugins under public/plugins without Composer package registration. This section explains how to build a basic plugin with lifecycle, hooks, views, and translations.
Recommended Structure
Use this structure:
public/plugins/{plugin_id}/
├── plugin.json
├── Plugin.php
├── classes/ (optional)
├── system/events.php
├── system/functions.php
├── routes/web.php
├── views/{backend|frontend|common}/...
└── lang/{en|zh_CN|zh_TW|ja}/word.phpPlugin Lifecycle
A plugin can implement lifecycle hooks through Plugin.php returning an instance extending Wncms\Plugins\AbstractPlugin.
Load extra plugin class files from root
Plugin.php.Keep
system/events.phpfor listeners andsystem/functions.phpfor helper functions only.plugin.jsonclassis optional whenPlugin.phpalready returns an instance.init()registers runtime hooks/events.activate()runs activation logic (for example default settings).deactivate()runs when plugin is disabled.delete()runs when plugin is removed.
Plugin Upgrade Lifecycle
Installed version is stored in plugins.version. Available package version is read from public/plugins/{plugin_id}/plugin.json version.
- If
plugin.jsonversion is newer thanplugins.version, update is available. - Plugin index keeps displaying database fields only.
- Editing
plugin.jsondirectly does not immediately overwrite plugin table display fields. - Upgrade is executed explicitly (backend upgrade action), then table metadata is synchronized from manifest.
- Plugin rows shown in
Plugins Indexmust come frompluginstable records. - Plugins found in
public/pluginswithout matchingplugin_idrecord are shown in a separateRaw Pluginstable. - After first activation creates a matching table record, plugin appears in the regular
Plugins Indextable.
Upgrade definition (deterministic map only)
Define upgrades in plugin lifecycle class:
public array $upgrades = [
'1.2.0' => 'upgrade_1_2_0.php',
'1.3.0' => 'upgrade_1_3_0.php',
];Execution rules:
- Only
$upgradesentries are executable. - Keys are target versions.
- Entries run in ascending version order.
- Runner executes steps where
installed_version < target_version <= available_version. - Upgrade files are resolved from
{plugin_root}/upgrades/. Bare filenames are prefixed to that directory at runtime. - If available version is newer but no chain reaches it, upgrade fails.
- On first failed step, process stops and installed version is unchanged.
- On success,
plugins.versionis updated to available version.
Activation Compatibility Checks
Before activation, WNCMS validates plugin dependency and version compatibility from plugin.json.
- Activation fails when a required dependency plugin is missing.
- Activation fails when a required dependency exists but is not active.
- Activation fails when a dependency version does not match the declared version constraint.
Supported dependencies formats in plugin.json:
{
"dependencies": ["plugin-a", "plugin-b"]
}{
"dependencies": {
"plugin-a": "^1.2",
"plugin-b": ">=2.0 <3.0"
}
}{
"dependencies": [
{ "id": "plugin-a", "version": "^1.2" },
{ "id": "plugin-b", "version": "~2.3" }
]
}Supported version constraint styles:
- Exact:
1.2.3 - Comparators:
>=1.2,<=2.0,!=1.4.0 - Ranges (space/comma separated):
>=1.2 <2.0 - Caret:
^1.2 - Tilde:
~1.4
Deactivation Safety Checks
Before deactivation, WNCMS checks whether the plugin is required by any other active plugins.
- If active dependent plugins are found, deactivation is blocked.
- Error message lists the dependent plugin ids and tells user to deactivate those plugins first.
Backend Plugin Index Visibility
Backend plugin index includes a Required Plugins column.
- It shows dependency plugin ids.
- If version constraints are defined, it shows
plugin_id (constraint). - Plugin index keeps diagnostics under
Remarkas a single detail button. - Clicking it opens one modal grouping
Last Load Error,Source File, and rawRemarkin terminal-style block output. Statuscolumn is hidden in index; activation state is inferred from action buttons (Activate/Deactivate).URL,Path, andRequired Pluginscolumns are only shown whenshow_detailis enabled.
Load failure remarks are stored in a structured format:
[LOAD_ERROR] YYYY-MM-DD HH:MM:SS {error_message} | source_file={absolute_file_path}