You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Plugins currently have no lifecycle hooks beyond init(). They can't run logic on load, register routes, add event listeners, or perform cross-plugin setup. This issue adds two optional lifecycle methods.
Proposed Hooks
onPluginLoad(app)
Called after plugin instantiation, before mixin processing
Receives an app context struct with access to: route mapper, event system, config
Use cases: register routes, add event listeners, declare config requirements
onPluginActivate()
Called after all plugins are loaded and all mixins injected
Use cases: cross-plugin setup, verify peer dependencies are active, warm caches
Contract
Both methods are optional. Existing plugins without them continue to work unchanged. This is the incremental stepping stone toward the register(container) / boot(app) Service Provider pattern in 4.0 (#1917).
Implementation
After CreateObject + init() in $pluginsProcess(), check for and call onPluginLoad(app)
After $initializeMixins() completes for all plugins, loop and call onPluginActivate()
Add two optional lifecycle hooks (onPluginLoad(app) and onPluginActivate()) to plugins, giving them the ability to register routes, add event listeners, and perform cross-plugin setup — capabilities that currently require mixin hacks or don't exist at all.
Merit Assessment
Recommendation: Proceed
Fills a real gap: Today plugins can only export functions via mixins. There is no way to run setup logic after load (e.g., register routes with the mapper) or coordinate between plugins. These two hooks address both timing windows.
Low risk, high leverage: Both hooks are optional — existing plugins without them are completely unaffected. The change is surgical: ~15 lines in $pluginsProcess() and ~10 lines in onapplicationstart.cfc.
Clean stepping stone to 4.0: The onPluginLoad(app) / onPluginActivate() pair maps directly to the register(container) / boot(app) Service Provider pattern in Plugin system → Service Provider model (Wheels 4.0) #1917. Plugins that adopt these hooks will need minimal changes when 4.0 lands.
Define the app context struct — Build a struct in $pluginsProcess() (or just before the loop) containing references to: the route mapper (application.$wheels.mapper — note: mapper is created after plugin load currently, see risk below), an event/config bag, and the environment name. This struct is the app argument to onPluginLoad.
Call onPluginLoad(app) in $pluginsProcess() (vendor/wheels/Plugins.cfc, line 162, after variables.$class.plugins[local.pluginKey] = local.plugin):
if (StructKeyExists(local.plugin, "onPluginLoad")) {
local.plugin.onPluginLoad(local.appContext);
}
Call onPluginActivate() in onapplicationstart.cfc — After the mixin initialization block (line 246), add a loop over application.$wheels.plugins:
for (local.pName in application.$wheels.plugins) {
if (StructKeyExists(application.$wheels.plugins[local.pName], "onPluginActivate")) {
application.$wheels.plugins[local.pName].onPluginActivate();
}
}
Tests — Add specs in tests/specs/functional/ that create mock plugin CFCs with/without the hooks and verify call order and argument contents.
Estimated complexity: Small (1-2 days).
Dependencies & Risks
Bootstrap ordering issue: The route mapper (application.$wheels.mapper) is created at onapplicationstart.cfc:250, after$loadPlugins() at line 237. If onPluginLoad(app) needs to register routes, the mapper must be instantiated earlier — or route registration must be deferred to onPluginActivate() (which runs after line 250). Recommend either (a) moving mapper creation before plugin load, or (b) documenting that route registration happens in onPluginActivate(), not onPluginLoad().
Implemented: onPluginLoad(app) called after init() with app context, onPluginActivate() called after all plugins loaded and mixins injected. Both optional, backwards compatible.
Activity
bpamiri commentedon Mar 16, 2026
Analysis
Synopsis
Add two optional lifecycle hooks (
onPluginLoad(app)andonPluginActivate()) to plugins, giving them the ability to register routes, add event listeners, and perform cross-plugin setup — capabilities that currently require mixin hacks or don't exist at all.Merit Assessment
Recommendation: Proceed
$pluginsProcess()and ~10 lines inonapplicationstart.cfc.onPluginLoad(app)/onPluginActivate()pair maps directly to theregister(container)/boot(app)Service Provider pattern in Plugin system → Service Provider model (Wheels 4.0) #1917. Plugins that adopt these hooks will need minimal changes when 4.0 lands.Implementation Plan
Define the
appcontext struct — Build a struct in$pluginsProcess()(or just before the loop) containing references to: the route mapper (application.$wheels.mapper— note: mapper is created after plugin load currently, see risk below), an event/config bag, and the environment name. This struct is theappargument toonPluginLoad.Call
onPluginLoad(app)in$pluginsProcess()(vendor/wheels/Plugins.cfc, line 162, aftervariables.$class.plugins[local.pluginKey] = local.plugin):Call
onPluginActivate()inonapplicationstart.cfc— After the mixin initialization block (line 246), add a loop overapplication.$wheels.plugins:Tests — Add specs in
tests/specs/functional/that create mock plugin CFCs with/without the hooks and verify call order and argument contents.Estimated complexity: Small (1-2 days).
Dependencies & Risks
application.$wheels.mapper) is created atonapplicationstart.cfc:250, after$loadPlugins()at line 237. IfonPluginLoad(app)needs to register routes, the mapper must be instantiated earlier — or route registration must be deferred toonPluginActivate()(which runs after line 250). Recommend either (a) moving mapper creation before plugin load, or (b) documenting that route registration happens inonPluginActivate(), notonPluginLoad().StructKeyExistson the CFC instance, so this can land independently.Automated analysis by @claude — review and adjust as needed.
bpamiri commentedon Mar 26, 2026
Implemented:
onPluginLoad(app)called after init() with app context,onPluginActivate()called after all plugins loaded and mixins injected. Both optional, backwards compatible.