Shell-Plugin Contract
The shell and plugins communicate through a strict, versioned contract. Plugins never import from the shell directly — all access goes through the SDK’s context objects and hooks, which the shell injects at runtime. Each plugin runs in its own host — errors in one plugin don’t crash others. IPC channels are automatically namespaced toplugin:{pluginId}:* to prevent collisions.
Plugin Lifecycle
Plugins go through four stages from discovery to teardown:1. Discovery
On startup, the shell scans the plugins directory and reads each plugin’splugin.json manifest. The sidebar order is determined by the VA owner’s configuration on the Skyvex platform.
2. Load
The shell loads the background module (if declared in the manifest) for each discovered plugin. This happens after auth is initialised, so the background module receives a fully populatedPluginContext.
If onStart() throws, the plugin is marked as errored. Other plugins continue loading normally.
3. Mount
When a pilot navigates to a plugin’s sidebar entry, the shell lazy-loads the UI module and mounts it inside aPluginShellProvider. This provider supplies PluginUIContext to all child components via React context.
The UI module is unmounted when the pilot navigates away, but the background module keeps running.
4. Unmount / Stop
On shell shutdown,onStop() is called for each running background module. Use this to close connections, flush data, or cancel timers.
After an OTA update, the shell calls onResume(ctx) on the background module if it’s defined — this lets you re-initialise without a full restart.
UI Module vs Background Module
| UI Module | Background Module | |
|---|---|---|
| Where it runs | Electron renderer process (React) | Electron main process (Node.js) |
| Entry point | src/ui/index.tsx | src/background/index.ts |
| Lifecycle | Mounted/unmounted on navigation | Runs from startup to shutdown |
| Context | PluginUIContext via usePluginContext() | PluginContext passed to onStart() |
| Use for | Display, interaction, flight data visualisation | Express routes, IPC handlers, background tasks |
PluginContext
Passed to your background module’s onStart(ctx) function. Provides scoped access to shell infrastructure:
plugin:{pluginId}:* — you can’t accidentally handle another plugin’s messages.
PluginUIContext
Available in renderer components via usePluginContext(). Provides synchronous access to the same capabilities as PluginContext, plus navigation and UI utilities:
config.get() is synchronous in the UI context (values are pre-loaded when the plugin mounts), while ctx.config.get() in the background context returns a Promise.
IPC Communication Model
The shell’s IPC bridge auto-prefixes all channel names to prevent cross-plugin pollution. When your background module registers a handler:plugin:my-plugin:get-status. Your UI module doesn’t need to know this — use the SDK’s usePluginIPC hook, which applies the same prefix automatically.
This means two plugins can both register a "get-status" handler without conflict.