README, getting-started, manifest-reference, and the contributor guide now cover the template matrix, drop-plugin dev, the settingsSchema contract, and the new auth:provider / storage:depot / game:runner capabilities.
9.5 KiB
Getting Started with Drop Plugins
Welcome to the Drop Plugin Developer Guide. This guide walks you through creating, testing, building, and deploying an extension for the Drop game distribution platform using the official @drop-oss/plugin-sdk toolchain.
Prerequisites
- Node.js: v22.0.0 or later
- Package Manager:
pnpm,npm, oryarn - Drop: Drop server v0.4.0+ or Drop Desktop client
1. Scaffolding a New Plugin
Use the Drop Plugin CLI (@drop-oss/plugin-cli) to scaffold a complete plugin repository with TypeScript, build configuration, schema validation, and unit tests:
# Scaffold a new plugin named "my-plugin" (default starter template)
npx @drop-oss/plugin-cli init my-plugin
cd my-plugin
pnpm install
Choosing a Template
drop-plugin init ships tailored templates so you start from the right SPI shape instead of trimming a generic one:
--template |
Use case |
|---|---|
starter |
Default hybrid server + client plugin (render-function UI slot). |
client-ui |
Desktop UI extension with real .vue Single File Components + CSS. |
metadata |
Server metadata provider (metadata:provider). |
store |
Client store library scanner (client:library-scan). |
runner |
Compatibility/emulation runner (game:runner). |
fullstack |
Server REST + declarative settingsSchema + Vue client UI. |
npx @drop-oss/plugin-cli init my-ui-plugin --template client-ui --id my-ui-plugin
Templates using .vue are compiled by drop-plugin build (Vue SFC + scoped CSS extraction); no extra Vite config is required.
Directory Structure
my-plugin/
├── drop-plugin.json # Plugin manifest (metadata, capabilities, entry points)
├── package.json # Node package dependencies
├── tsconfig.json # TypeScript compiler configuration
├── src/
│ ├── index.ts # Server-side entry point (implements ServerPlugin)
│ └── client.ts # Desktop client-side entry point (implements ClientPlugin)
└── test/
└── index.test.ts # Unit tests using MockPluginContext & MockClientPluginContext
2. Implementing Server Logic
Server plugins run within Drop's Nitro / Node.js runtime. They can register HTTP routes, maintain isolated persistent storage, broadcast events, and establish real-time WebSocket channels.
In src/index.ts:
import type { PluginContext, ServerPlugin } from "@drop-oss/plugin-sdk";
export default class MyPlugin implements ServerPlugin {
metadata = {
id: "my-plugin",
name: "My First Plugin",
version: "1.0.0",
apiVersion: 2,
targets: ["server" as const],
capabilities: ["routes" as const, "storage" as const, "websocket" as const],
};
async init(ctx: PluginContext): Promise<void> {
ctx.logger.info("Initializing MyPlugin on Drop server...");
// Register a custom REST route under /api/v1/plugins/my-plugin/status
ctx.registerRoute("GET", "/status", async () => {
const launches = (await ctx.storage.get<number>("launch_count")) ?? 0;
return { status: "online", launches };
});
// Register a real-time WebSocket channel
ctx.registerWebSocket("my-plugin:events", (msg, wsCtx) => {
ctx.logger.info(`Received WS message: ${JSON.stringify(msg)}`);
wsCtx.send({ ack: true, timestamp: Date.now() });
});
}
async teardown(): Promise<void> {
// Cleanup any background timers or resources
}
}
3. Implementing Desktop Client Logic
Client plugins run within the Drop Tauri desktop webview. They can inject Vue components into UI slots, contribute dynamic Play Actions, and intercept game launches with fail-closed rollbacks.
In src/client.ts:
import type {
ClientPlugin,
ClientPluginContext,
LaunchContext,
} from "@drop-oss/plugin-sdk";
export default class MyClientPlugin implements ClientPlugin {
metadata = {
id: "my-plugin",
name: "My First Plugin (Client)",
version: "1.0.0",
apiVersion: 2,
targets: ["client" as const],
capabilities: [
"ui:slot" as const,
"ui:play-action" as const,
"game:launch-hook" as const,
],
};
async init(ctx: ClientPluginContext): Promise<void> {
ctx.logger.info("Initializing MyClientPlugin on desktop client...");
// 1. Inject a panel into the game details page
ctx.registerSlot(
"game-detail:panels",
{
// Use a render function: production builds ship no Vue runtime
// template compiler, so raw `template:` strings are rejected.
render() {
const h = (globalThis as Record<string, any>).window?.Vue?.h;
return h(
"div",
{
class:
"rounded-lg border border-purple-500/30 bg-purple-500/10 p-4 text-xs text-purple-200",
},
[
h("p", { class: "font-semibold" }, "My Custom Plugin Panel"),
h(
"p",
{ class: "text-zinc-400 mt-1" },
"Enhancing your game library experience.",
),
],
);
},
},
{ label: "My Plugin", order: 5 },
);
// 2. Add an alternative Play Action in the Play button dropdown
ctx.registerPlayAction((gameId: string) => [
{
id: "play-special",
name: "Play (Custom Mode)",
isDefault: false,
execute: async (context: LaunchContext) => {
ctx.logger.info(`Launching ${context.gameTitle} in custom mode`);
},
},
]);
// 3. Register a pre-launch hook to validate preconditions
ctx.registerLaunchHook({
stage: "pre-launch:validate",
order: 10,
execute: async (context: LaunchContext) => {
ctx.logger.info(
`Validating prerequisites for game ${context.gameId}...`,
);
},
});
}
}
4. Testing with Mock Harnesses
Drop Plugin SDK provides high-fidelity in-memory mock harnesses (MockPluginContext and MockClientPluginContext) that enforce capability gating and simulate runtime environments without requiring a running Drop instance.
In test/index.test.ts:
import { test } from "node:test";
import assert from "node:assert/strict";
import {
MockPluginContext,
MockClientPluginContext,
} from "@drop-oss/plugin-sdk";
import MyPlugin from "../src/index.js";
import MyClientPlugin from "../src/client.js";
test("MyPlugin server registers route and storage", async () => {
const ctx = new MockPluginContext("my-plugin", [
"routes",
"storage",
"websocket",
]);
const plugin = new MyPlugin();
await plugin.init(ctx);
assert.equal(ctx.routes.has("GET /status"), true);
const handler = ctx.routes.get("GET /status")!;
const response = (await handler({} as any, { params: {}, query: {} })) as any;
assert.equal(response.status, "online");
});
test("MyClientPlugin registers UI slots and play actions", async () => {
const ctx = new MockClientPluginContext("my-plugin", [
"ui:slot",
"ui:play-action",
"game:launch-hook",
]);
const plugin = new MyClientPlugin();
await plugin.init(ctx);
assert.equal(ctx.registeredSlots.get("game-detail:panels")?.length, 1);
assert.equal(ctx.playActionProviders.length, 1);
});
Run tests using the CLI:
npx drop-plugin test .
5. Building, Validating & Packaging
Step A: Build
Bundle your TypeScript source into optimized, standalone ESM bundles:
npx drop-plugin build .
This produces the entry points declared in drop-plugin.json (server.entry / client.entry, e.g. dist/src/index.js and dist/src/client.js) and writes the SHA-256 digests into drop-plugin.json. Pass --out-manifest <path> to keep the derived digests out of the source manifest.
Step B: Validate
Validate the manifest against the official JSON Schema:
npx drop-plugin validate .
Step C: Sign (Optional for trusted deployments)
Calculate HMAC-SHA256 signature if an administrator key is configured:
export DROP_PLUGIN_SIGNING_KEY="your-signing-secret"
npx drop-plugin sign .
Step D: Package
Package the bundle into a distributable .dropplugin archive:
npx drop-plugin pack . ./dist-package
This generates dist-package/my-plugin-1.0.0.dropplugin.
Step E: Live Development (drop-plugin dev)
For fast iteration, run the watch server. It rebuilds server and client entries on save, signs the bundle, and serves it over CORS-enabled local HTTP so Drop Desktop can load it without a repack:
npx drop-plugin dev . --port 4567
# [drop-plugin] Client bundle URL: http://localhost:4567/dist/src/client.js
Point Drop Desktop's Extension Settings → Load Dev Plugin at the printed client bundle URL; edits hot-reload on the next reload.
6. Installing into Drop
Via Web Admin Settings
- Log in as an administrator on your Drop server.
- Navigate to Admin Settings → Plugins.
- Under Install Plugin Bundle, upload your
.droppluginfile or enter its download URL. - Click Install. The plugin initializes dynamically without server restart.
Via Desktop Settings
- Open Drop Desktop.
- Go to Settings → Plugins & Extensions.
- Click Upload .dropplugin / JSON to select your package.