Plugin states
The runtime tracks each plugin in one of these states:| State | Description |
|---|---|
| registered | Discovered and in the registry; not yet loaded. |
| loading | Dependency resolution and child process creation in progress. |
| loaded | Child process is up; plugin module loaded; not yet activated. |
| activating | Main process has sent activate command; waiting for plugin to finish activate(). |
| active | Plugin has completed activate() and is running. Can receive events and requests. |
| deactivating | Main process has sent deactivate command; plugin is running deactivate(). |
| inactive | Plugin has completed deactivate(); may still be in memory or process. |
| unloading | Process is being torn down (e.g. plugin disabled or app shutdown). |
| unloaded | Child process has been destroyed; plugin is no longer running. |
| failed | Error during load, activation, or health check; plugin is not active. |
Activation flow
- Load decision – The app decides to load the plugin (e.g. it’s enabled and its dependencies are satisfied). Plugin state moves to loading.
- Dependencies – All manifest dependencies (other plugins, optional executables) are resolved. If a required dependency is missing or fails to load, this plugin does not activate (and may be marked failed or skipped).
- Child process – The runtime spawns a Node.js child process for this plugin, passing
PLUGIN_IDandPLUGIN_PATH. The child loads the plugin loader script, which then loads your main module and instantiates your class with the Plugin API. State becomes loaded. - Activate command – The main process sends an activate message to the child with plugin id and path. The child calls activate() on your plugin instance (with timeout and error handling so a bad
activate()doesn’t hang the process). - Your activate() – You should:
- Call await super.activate() first.
- Set up any in-process state (e.g. subscribe to events with this.api.events.on() if not using manifest subscriptions only).
- Optionally read this.api.config.getConfig(), this.api.hasDependency(), or this.api.getTempDirectory() (if available in the child).
- Return (or resolve the promise). If you throw or reject, activation is reported as failed and the plugin moves to failed.
- Active – The child signals activation success to the main process. State becomes active. The plugin now receives events (including manifest subscriptions) and can be called by other plugins via requestFromPlugin / respond.
Deactivation flow
- Deactivate trigger – The user disables the plugin, or the app is shutting down, or the plugin is being unloaded for reload. State moves to deactivating.
- Deactivate command – The main process sends a deactivate message to the child. The child calls deactivate() on your plugin instance.
- Your deactivate() – You should:
- Release resources (e.g. unsubscribe from events you added in activate() with the function returned from api.events.on()). Note: subscriptions declared in the manifest are managed by the runtime; you don’t need to unsubscribe from those.
- Close handles, clear timers, etc.
- Call await super.deactivate().
- Return (or resolve). The runtime will not wait forever; there is a deactivation timeout.
- Inactive / Unload – The child signals deactivation success. The main process may then tear down the child process (state unloading → unloaded). If the plugin is only being disabled (not unloaded), it may remain inactive until the process is later destroyed.
Cleanup and best practices
- Subscriptions in code – If you call this.api.events.on(…) in activate(), store the returned unsubscribe function and call it in deactivate(). For subscriptions in the manifest, the runtime cleans up when the plugin is deactivated; you don’t need to unsubscribe manually.
- No mandatory unsub for plugin.activated – Subscriptions to system events like plugin.activated are also cleaned up when the plugin is deactivated; you don’t have to unsubscribe explicitly.
- Avoid long work in activate/deactivate – Keep both methods relatively fast. Do heavy or long-running work asynchronously after activation (e.g. in response to an event or context menu).
- Errors – If activate() or deactivate() throws, the runtime catches it, logs it, and reports failure. The plugin may end up in failed or unloaded state; the app may retry loading later (e.g. on health check or user “reload”).
Health checks and reload
The app may run a periodic health check on active plugins (e.g. ping the child process). If the plugin process dies or stops responding, the app may mark it failed and optionally attempt to reload it (unload then load again). After a limited number of reload retries, the plugin may stay in failed until the user intervenes.Summary
- States: registered → loading → loaded → activating → active → deactivating → inactive → unloading → unloaded (or failed on error).
- activate(): Call super.activate(), set up listeners and state, return. Don’t block too long.
- deactivate(): Clean up listeners you added in code, release resources, call super.deactivate().
- Manifest subscriptions and system events like plugin.activated are cleaned up by the runtime; you only need to unsubscribe for api.events.on() you add yourself.