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 (ifbackground/index.js exists in the plugin’s install directory) for each discovered plugin. This happens after auth is initialised, so the background module receives a fully populated PluginContext.
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.
UI Module vs Background Module
Most plugins only need a UI module. Create a background module when you need persistent server-side logic — registering API routes, maintaining a WebSocket connection, or running scheduled 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:
vaApi is a pre-authenticated Axios instance for the bound airline’s API, shared with the shell so concurrent 401s coalesce into a single token refresh. Reach it through the useVaApi() hook rather than reading it off the context directly.
Note that 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. This means two plugins can both register a "get-status" handler without conflict.
To communicate between your UI and background modules, register Express routes in the background module via ctx.server.registerRouter() and fetch them in the UI with TanStack Query. This keeps data flowing through standard HTTP rather than requiring a custom IPC abstraction.