Skip to main content
Plugins are built with Vite using a shared config factory from the SDK. The output is zipped and uploaded to the Skyvex Software website for distribution.

Vite Configuration

Use createPluginConfig() from the SDK:
The vite option accepts any Vite config to merge in: plugins, resolve aliases, CSS config, etc.
@vitejs/plugin-react is required, not optional. The SDK injects a React Fast Refresh preamble into your entry file that imports from /@react-refresh: an endpoint only served when react() is registered. Without it, pnpm dev against a running Stratos will fail to load the plugin UI with a cross-origin module error.
If you only have a UI module, omit the background field.

Build Scripts

With background module:
UI-only plugin:
Use cross-env for Windows compatibility with the BUILD_TARGET environment variable.

How the Build Works

UI build (default): Compiles your React code into a single ESM bundle. Shared dependencies are externalised and resolved from the shell at runtime via window.__stratos_modules__. The UI bundle is always one file: dynamic import()s are inlined rather than split into chunks, because Stratos can load ui/index.js from a blob: URL, where relative chunk imports can’t resolve. Don’t override build.rollupOptions.output to split it. process.env.NODE_ENV is replaced with the build mode (production for vite build) and any other process.env read is undefined, so libraries that check it work without a process shim. There is no Node process in the renderer. Background build (BUILD_TARGET=background): Compiles your Node.js module into an ESM bundle. Electron, Node.js builtins, and the SDK are externalised.

CSS and Static Assets

Plugins are loaded at runtime via import(), so CSS and assets must be embedded in the JS bundle: separate files won’t be loaded. CSS injection is handled automatically. Any CSS you import (including Tailwind output from @tailwindcss/vite) is injected into the DOM as a <style> tag when your plugin module loads. The shell wraps all plugin CSS in @layer plugin to prevent your styles from overriding the shell’s UI: you don’t need to do anything special for this. Your plugin’s styles.css should include Tailwind and a @source directive so Tailwind can find your component files:
src/ui/styles.css
Import it in your plugin’s entry file:
Static assets (images, SVGs, fonts) under 10 MB are inlined as data URIs automatically. Import them as usual and use the result as a src:
This works for any file type Vite can handle (.svg, .png, .jpg, .webp, .woff2, etc.).

What Gets Externalised

You do not need to bundle these: the shell provides them at runtime: UI externals:
  • react, react-dom
  • @tanstack/react-query, @tanstack/react-router
  • @skyvexsoftware/stratos-sdk
  • sonner, socket.io-client
  • maplibre-gl, react-map-gl/maplibre
Background externals:
  • electron
  • @skyvexsoftware/stratos-sdk (except /helpers, which is bundled into your plugin)
  • socket.io-client
  • All Node.js built-in modules
Anything else you import (e.g. lucide-react, your own utilities) will be bundled into your plugin. Named lucide-react imports are tree-shaken, so only the icons you use are included. For icons chosen by name at runtime, use DynamicIcon from @skyvexsoftware/stratos-sdk/icons, which loads them from Stratos (core 2.49.0+) instead of bundling the whole set.

Build Output

After pnpm build, the dist/ directory contains:
CSS and imported static assets are embedded directly in ui/index.js: there are no separate .css files in the output.

Development Workflow

Live Reload Development

The recommended development workflow uses the Stratos app’s developer mode:
  1. Open Stratos with the --dev flag
  2. Sign in and select your airline
  3. In your plugin directory:
Your plugin’s Vite dev server auto-connects to Stratos via Socket.io. The plugin appears in the sidebar immediately. Code changes auto-reload in about a second.

Manual Testing (No Dev Server)

If you prefer to test with a production build:
  1. Run pnpm build
  2. Copy the dist/ contents to Stratos’s plugins directory:
  1. Restart Stratos: it discovers plugins by finding plugin.json in each subdirectory.
Tip: Symlink your dist/ directory to skip the copy step:

Deploying

Quick Deploy

The SDK includes a stratos-deploy CLI that bundles and uploads your plugin in one step:
This runs pnpm build, zips dist/ into bundle.zip (leaving out source maps), and uploads it to the Skyvex Software API. Your package.json should have:
If you scaffolded your plugin with create-stratos-plugin, this script is already included. Add --release-mode=manual to stratos-deploy to hold an approved version until you press Release in the Developer centre. Source maps are optional. Reviewers always read the bundle that runs, so a bundle without maps is reviewed as it is. If you do include maps (in your own zip or a Developer centre upload), build them in the same run as the bundle and keep sourcesContent: we check every map against the code it describes, and a map that doesn’t match holds the version for a closer look by a person. See Plugin review.

Authentication

Create an API token in the Developer centre at skyvexsoftware.com/developer/tokens (Developer centre → API tokens) and tick the plugin:upload scope.
Create a token, with the plugin:upload scope tickedCreate a token, with the plugin:upload scope ticked
Then set it as an environment variable:
The token must carry the plugin:upload scope. Uploads made with a token that lacks it are rejected with 403 Invalid scope(s) provided. Scopes are fixed when a token is created, so if your token doesn’t have it, create a new one with plugin:upload ticked and revoke the old one. The plugin ID and version are read from plugin.json automatically. On success, the version goes to review, public or private, then the Skyvex Software server signs and publishes it to the CDN once it’s approved.

What’s new

Every version needs a What’s new before it goes to review: the reviewer reads it, and pilots see it with the update. It’s entered on Skyvex Software, never read from the bundle. Type it in the Developer centre when you publish, or send it with the upload. With pnpm bundle, set SKYVEX_WHATS_NEW:
If you upload with your own script, send it as the changelog form field alongside file. An upload without one isn’t rejected. It waits as a draft in the Developer centre until you add a What’s new there and submit it, and you get an email saying so. The upload response’s status is draft, pending_review or approved.

Manual Upload

If you don’t have an API token or prefer to upload through the website:
Without SKYVEX_API_TOKEN, the command still builds and creates bundle.zip: it just skips the upload and tells you where to upload manually at skyvexsoftware.com.

CI/CD

For automated deployments, set SKYVEX_API_TOKEN as a secret in your CI environment:

Full GitHub Actions Workflow

Drop this in your plugin repo at .github/workflows/deploy.yml for a complete deploy pipeline that builds the bundle and uploads it to Skyvex Software on every push to main:
.github/workflows/deploy.yml
Setup steps:
  1. Add the workflow file at .github/workflows/deploy.yml in your plugin repo.
  2. Generate an API token in the Developer centre under API tokens, with the plugin:upload scope ticked.
  3. Add the secret in your GitHub repo: Settings → Secrets and variables → Actions → New repository secret. Name it SKYVEX_API_TOKEN and paste the token.
  4. Bump plugin.json version before each push. An approved version can’t be replaced.
  5. Push to main (or trigger manually from the Actions tab). The workflow builds the bundle and uploads it for review; once it’s approved, the Skyvex Software API handles signing and CDN publishing.
The workflow only runs when files that affect the bundle change: edits to README.md or unrelated files won’t trigger a deploy. Use workflow_dispatch to force a run from the Actions tab.

What Happens After Upload

  1. The Skyvex Software API checks your plugin.json (snake_case keys only, no unknown keys, valid settings)
  2. With a What’s new the version goes to review; without one it waits as a draft
  3. Once it’s approved and released, an .integrity file is generated with SHA256 hashes for every file in your bundle
  4. The bundle is signed with Ed25519 and published to the CDN, and latest.json is updated
  5. Pilots receive the update automatically

Troubleshooting

Airline Plugin Sync

When a plugin has "type": "airline", the VA platform manages distribution. Stratos automatically syncs the plugin set on authentication:
  1. The pilot signs in to their airline
  2. Shell fetches the airline’s public plugin list (every plugin the VA has installed for everyone, plus core plugins)
  3. After relay-token authentication, the shell fetches the pilot-scoped list: this returns the airline-wide plugins plus any extras the VA admin has assigned to that specific pilot
  4. Missing plugins are downloaded and installed automatically; removed plugins are cleaned up
  5. Updates are applied automatically when new versions are published
VA admins choose, per plugin, whether to install it for all pilots or only for selected pilots on the airline’s Plugins page at skyvexsoftware.com. Pilot-specific assignments only appear in the post-sign-in (relay-authenticated) plugin list, never in the public airline list. For "user" type plugins, pilots install them manually.