Background modules run in the Electron main process (Node.js). They’re optional — only add one when you need persistent server-side logic that outlives UI navigation: registering Express routes, maintaining a connection, or running scheduled tasks.Documentation Index
Fetch the complete documentation index at: https://docs.skyvexsoftware.com/llms.txt
Use this file to discover all available pages before exploring further.
When to Use a Background Module
Use a background module when you need to:- Expose an HTTP API to your UI module (via Express routes)
- Handle IPC messages from the renderer
- Maintain a persistent connection (WebSocket, database, etc.)
- Run background polling or scheduled work
- Access Node.js APIs not available in the renderer
Declaring a Background Module
Add thebackground entry to your Vite config:
background/index.js in your plugin’s install directory — no plugin.json configuration is needed.
The PluginBackgroundModule Contract
Your background entry module must export onStart and onStop. Use the createPlugin helper for type safety and validation:
createPlugin helper validates your module at import time and gives you full type inference on ctx — no manual type imports needed. The shell requires this default export pattern; named exports are not supported.
PluginContext Fields
The ctx object passed to onStart() and onStop() provides scoped access to shell infrastructure:
ctx.logger — PluginLogger
Standard log methods, automatically prefixed with your plugin ID in the log output:
ctx.config — PluginConfigStore
Persistent key-value store namespaced to your plugin. All methods are async. Values survive shell restarts:
config.get() is async in the background context. In the UI context (via usePluginContext()), it’s synchronous.
ctx.ipc — PluginIPCRegistrar
Register IPC handlers and send messages to the renderer. All channel names are automatically prefixed as plugin:{pluginId}:*:
plugin:my-plugin:get-status and plugin:my-plugin:new-flight-available — the prefix is applied transparently.
ctx.auth — PluginAuthAccessor
Read-only access to the current authentication state:
ctx.airline.createClient() below — it adds refresh-on-401 handling that ctx.auth.getToken() does not.
ctx.airline — PluginAirlineAccessor
Read-only access to the currently-bound airline plus a pre-configured HTTP client:
getCurrent()
Returns the current airline binding. Re-call it when you need a fresh snapshot — there’s no event subscription.
createClient() — recommended
Returns an axios instance pre-configured for the bound airline:
baseURLis set toairline.baseUrl(resolved per request).Authorizationheader carries the current VA bearer.- On a
401response, the shell exchanges the stored refresh token at the VA’srefresh_url. If refresh succeeds, the request is retried exactly once. If the VA didn’t configure arefresh_url, the user has no refresh token, or the VA rejected the refresh, the401propagates and the shell’s existing re-auth flow takes over.
ctx.database — PluginDatabaseAccessor
Open (or create) an SQLite database file scoped to your plugin’s data directory at {userData}/plugins-data/{pluginId}/. Returns a better-sqlite3 Database instance:
ctx.server — PluginServerRegistrar
Register Express routers on the shell’s internal HTTP server:
/api/my-plugin/status — use STRATOS_APP_BASE from the SDK to build the full URL. Use TanStack Query in your UI module to fetch from these endpoints.
Registering Express Routes
A common pattern is to fetch data from your VA’s external API in the background module and expose it locally:Communication Between Background and UI
For UI-to-background communication, the recommended pattern is Express routes + TanStack Query. Register routes in your background module withctx.server.registerRouter() and query them from your UI module using TanStack Query, as shown in the Registering Express Routes section above.
This approach composes naturally with React’s rendering model — loading states, error handling, caching, and refetching all come for free via TanStack Query.
For background-to-UI push (data the UI didn’t ask for, like live updates or alerts), you can use ctx.ipc.send(). UI modules subscribe with socket.on from usePluginContext() — both ends use the leaf event name, the shell scopes the channel to your plugin id automatically.
- Leaf names only. Don’t include
plugin:{id}:in the channel — the shell prefixes both ends, doubling it would broadcast to nothing. - Fire-and-forget. A UI that mounts after a broadcast doesn’t see it. Pattern: HTTP fetch on mount, then subscribe for updates.
- Same plugin only. Plugin A’s UI cannot subscribe to plugin B’s broadcasts.
- No UI → background
socket.emit. Use HTTP viactx.server.registerRouterinstead. - Structured-cloneable payloads. Functions, DOM nodes, and class instances with methods do not survive Electron IPC serialization.
Lifecycle Summary
- Shell starts and initialises auth
- Shell calls
onStart(ctx)for each plugin with a background module - If
onStart()throws, the plugin is marked errored — other plugins still load - On shutdown, shell calls
onStop(ctx)for each running background module - After an OTA update, the shell calls
onStop(ctx)thenonStart(ctx)fresh