Skip to content

Plugin lifecycle hooks: onPluginLoad, onPluginActivate #1979

Closed

Description

@bpamiri

Summary

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()

Files

  • vendor/wheels/Plugins.cfc:146-178 — extend $pluginsProcess()
  • vendor/wheels/events/onapplicationstart.cfc:235-246 — add post-load activation loop

Phase

Phase 2 — DI Integration & Lifecycle (3.x, backward compatible)

Related

Activity

  1. bpamiri commented on Mar 16, 2026

    @bpamiri
    CollaboratorAuthor

    Analysis

    Synopsis

    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.
    • Unlocks Plugin DI integration — register services via Injector #1980 and Plugin middleware registration #1981: DI integration and middleware registration both need a hook that runs during plugin loading. This issue is the prerequisite.

    Implementation Plan

    1. 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.

    2. 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);
      }
      
    3. 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();
          }
      }
      
    4. 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().
    • No dependency on Add plugin.json manifest support alongside box.json #1976 (plugin.json) — hooks are discovered via StructKeyExists on the CFC instance, so this can land independently.
    • Prerequisite for: Plugin DI integration — register services via Injector #1980 (DI integration), Plugin middleware registration #1981 (middleware registration).
    • Breaking change risk: None — purely additive.

    Automated analysis by @claude — review and adjust as needed.

  2. added this to the v3.2 milestone on Mar 17, 2026
  3. bpamiri commented on Mar 26, 2026

    @bpamiri
    CollaboratorAuthor

    Implemented: onPluginLoad(app) called after init() with app context, onPluginActivate() called after all plugins loaded and mixins injected. Both optional, backwards compatible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions