Skip to content

Plugins

Zephyr ships with a community plugin system. Plugins live in a separate repo, Zephyr-plugin, and Zephyr fetches the registry from there on demand.

Plugin types

TypeWhat it isFiles it ships
ThemeA CSS file applied to Zephyr's UI.theme.css
FeatureA full mini-app running in a sandboxed iframe with its own sidebar item.dist/index.html (built from Svelte)
GameA new game definition Zephyr can target.game.json
ModA downloadable mod for a registered game (see Publishing your mod).manifest only

Themes restyle Zephyr. Features are real apps: you write Svelte 5, build to a single HTML file, and Zephyr loads it in a sandboxed iframe with a postMessage bridge for host capabilities.

Installing a plugin from the registry

Plugins page

  1. Open the Plugins page in Zephyr.
  2. Browse the community sections. Click Install on the card.
  3. Features get a new sidebar item; themes can be picked in Settings → Appearance.
  4. Uninstall any time from the same page.

Click Refresh registry if a freshly published plugin doesn't appear. The CDN refreshes within a few minutes.

Writing a plugin

Everything happens inside the Zephyr-plugin repo.

bash
git clone https://github.com/Prismo-Studio/Zephyr-plugin.git
cd Zephyr-plugin
pnpm install
pnpm new-plugin    # interactive wizard

The wizard asks for:

  • Type: Theme (CSS only) or Feature (full Svelte app)
  • Display name, slug (folder name), author (GitHub username), description
  • Sidebar label (features only, max 18 chars)

It scaffolds themes/<slug>/ or features/<slug>/ with a working starter you can edit immediately.

Live preview in Zephyr (Dev Mode)

You don't need to publish a PR to test. In Zephyr:

Load plugin button

  1. Plugins → Dev Mode → Load plugin
  2. Pick your features/<slug>/ (or themes/<slug>/) folder
  3. For features: pnpm dev inside the folder. Vite watches, Zephyr's filesystem watcher reloads the iframe on every save
  4. For themes: edit theme.css. Zephyr re-injects it automatically

A purple DEV badge appears on the card so you know it's loaded from disk rather than the registry.

The Plugin SDK

Every feature plugin imports @zephyr-plugin/sdk (set up automatically by the wizard).

Components matching Zephyr's look

svelte
<script lang="ts">
  import { Toggle, Select, Button, Card, Row, StatusPill, StatCard } from '@zephyr-plugin/sdk';
</script>

<Row title="Enable">
  {#snippet control()}<Toggle bind:checked={enabled} />{/snippet}
</Row>

Available components: Toggle, Select, Button, Card, Row, StatusPill, StatCard.

Import @zephyr-plugin/sdk/tokens.css to inherit Zephyr's color tokens. Your plugin will track the user's active theme automatically.

Host bridge: zephyr.*

ts
import { zephyr } from '@zephyr-plugin/sdk';

// Per-plugin key/value storage
await zephyr.storage.set({ enabled: true });
const state = await zephyr.storage.get<{ enabled: boolean }>();

// Per-plugin file storage
const filename = await zephyr.fs.writeBlob('clip.mp4', blob);
const files = await zephyr.fs.list('.mp4');
const url = await zephyr.fs.getUrl('clip.mp4');   // file:// URL safe in <video>
await zephyr.fs.delete('clip.mp4');
await zephyr.fs.openFolder();                     // reveal in OS file manager

// Toasts and external links
await zephyr.notify('Done', { kind: 'info' });
await zephyr.openExternal('https://...');

// Plugin metadata (id, name, version, dev flag)
const info = await zephyr.plugin();

Everything is per-plugin. A plugin cannot read another plugin's storage, write outside its own folder, spawn processes, or access Zephyr's mod data.

What a feature plugin can do natively

The iframe is a normal web context with these permissions enabled:

  • fetch() and WebSocket to any server (no CORS bypass, same rules as a browser)
  • getDisplayMedia for screen recording, getUserMedia for mic/camera
  • IndexedDB, localStorage, canvas, Web Audio, WebGL
  • Single-file HTML bundles up to ~10 MB

Not available without an SDK extension: reading installed mods, hooking into game-launch events, running native code, patching binaries.

Manifest reference

FieldRequiredApplies toNotes
idyesalllowercase slug (a-z, 0-9, -). Must match the folder name.
nameyesalldisplay name in the plugin browser.
versionyesallsemver, e.g. 1.0.0.
typeyesallone of theme, feature, game, mod.
author.nameyesallshown on the plugin card.
author.urlnoallprofile or project link.
descriptionyesallone or two sentences.
iconnoallfilename in your folder, an absolute URL, or an Iconify id (mdi:palette). Shown on the plugin card.
entryyesthemesfilename of the CSS asset (theme.css).
entrynofeaturespath to the built HTML, defaults to dist/index.html.
sidebarLabelnofeaturesshort label for the sidebar item (max 18 chars). Falls back to name.
sidebarIconnofeaturesIconify id (mdi:record-rec, mdi:video, …). Defaults to mdi:puzzle-outline.
defaultInstallednofeaturesbool, default false.
removablenofeaturesbool, default true.
package.urlyesmodsURL of the mod archive.
package.sha256yesmodsSHA-256 of the archive.
gameyesmodsslug of the target game.

Example feature manifest

json
{
  "id": "captures",
  "name": "Captures",
  "version": "1.0.0",
  "type": "feature",
  "author": { "name": "MathisBls" },
  "description": "Auto-record gameplay when you launch a modded game.",
  "icon": "icon.svg",
  "sidebarLabel": "Captures",
  "sidebarIcon": "mdi:record-rec",
  "entry": "dist/index.html"
}

Publishing a plugin

  1. Open a PR against Zephyr-plugin adding your folder under features/, themes/, games/, or mods/.
  2. For features: commit your built dist/index.html along with the source. The registry build does not run plugin builds.
  3. CI regenerates registry.json after merge.
  4. Within ~5 minutes (CDN refresh) the plugin appears in every Zephyr install's plugin browser.

See the Zephyr-plugin README for the full contribution flow and validation commands.

Released under GPL-3.0.