HyperHQ Plugin API Reference
Quick reference for the current HyperHQ plugin runtime.
Manifest file
HyperHQ discovers and validates plugins from plugin.json in each plugin directory. manifest.json is not used for plugins.
Required manifest fields
{
"id": "my-plugin",
"name": "My Plugin",
"version": "1.0.0",
"description": "Short user-facing description",
"author": "Your Name",
"type": "executable",
"executable": "plugin.exe",
"communication": {
"preferred": "stdio",
"fallback": "socketio",
"stdio": { "enabled": true },
"socketio": { "enabled": true }
},
"capabilities": [
{ "name": "utility", "description": "Adds a utility action", "required": true }
],
"permissions": []
}
| Field | Notes |
|---|---|
id | Lowercase letters, numbers, hyphens, and underscores. |
name, version, description, author | Required and non-empty. version must be semver-like, e.g. 1.0.0. |
type | javascript or executable. |
main | JavaScript entry file (.js or .mjs); defaults to index.js when omitted. |
executable | Executable plugin fallback entry point. Defaults to plugin.exe on Windows and plugin on Linux/macOS if omitted in some runners. |
executableProviders | Preferred platform-specific executable map: { "windows": "plugin.exe", "linux": "plugin", "macos": "plugin" }. |
executableLinux, executableMac | Legacy flat platform overrides still supported for compatibility. Prefer executableProviders. |
communication | Optional executable transport preference containing preferred, fallback, stdio, and socketio. |
capabilities | Required non-empty array of capability objects. |
permissions | Array of permission objects, not an object map. Empty array is allowed. |
platforms | Optional array: windows, linux, macos. Omit for all platforms. |
Permissions
{
"permissions": [
{ "type": "network", "scope": "api.example.com", "description": "Fetch metadata" },
{ "type": "file", "scope": "plugin", "description": "Read packaged plugin assets" }
]
}
Supported permission type values are file, network, system, and database. scope and description are required. Avoid broad scopes such as *; unrestricted system access is rejected.
Settings schema
Settings are declared in top-level settings. Use label for display text; name is not the current setting label field.
| Type | Use for |
|---|---|
string, textarea | Text input. |
number, range | Numeric input; use validation min, max, step. |
boolean | Toggle/checkbox. |
email, password, url | Specialized text inputs. |
select | Choice list via options: [{ "value": "...", "label": "..." }]. |
oauth | HyperHQ-managed OAuth/browser capture flow. |
array, object | Structured settings. |
file | File selector; use validation.fileTypes to filter extensions. |
directory | Folder selector. |
color | Color picker. |
Path picker fields:
fileopens a file picker.directoryopens a folder picker.stringremains a text field, but acceptsbrowsefor optional path picking.browse: truedefaults to a file picker unless the setting type isdirectory.browse: "file"orbrowse: "directory"selects the picker mode.browse: { "type": "directory", "buttonLabel": "Choose Folder" }changes the button text.
{
"settings": [
{
"key": "apiKey",
"type": "password",
"label": "API Key",
"description": "Token for the metadata service",
"required": true,
"validation": { "minLength": 20 }
},
{
"key": "accentColor",
"type": "color",
"label": "Accent Color",
"defaultValue": "#7c3aed"
},
{
"key": "account",
"type": "oauth",
"label": "Account",
"oauth": {
"provider": "example",
"authUrl": "https://example.com/oauth/authorize",
"callbackUrls": ["https://example.com/oauth/callback"],
"requiredCallbackParams": ["code"],
"statusAction": "oauth.status"
}
},
{
"key": "installPath",
"type": "string",
"label": "Install Path",
"browse": "directory",
"placeholder": "Select or enter an install folder"
},
{
"key": "mameExe",
"type": "file",
"label": "MAME Executable",
"validation": {
"fileTypes": [
{ "name": "Executables", "extensions": ["exe"] }
]
}
}
]
}
oauth settings are passed to plugins as runtime connection state/artifacts after HyperHQ captures the OAuth result. Plugins should treat missing/expired OAuth state as recoverable configuration failure.
Required plugin methods
Both executable and JavaScript plugins are called with the same method names.
| Method | Required | Purpose |
|---|---|---|
initialize(data) | Yes | Receive settings and plugin metadata. |
execute(data) | Yes | Perform an action or main behavior. |
test(data) | Yes | Return health (true/false or equivalent result). |
shutdown(data) | No | Clean up before stop. |
For stdio plugins that emit a complete PluginResponse, a startup configuration failure can be reported with top-level status: "requiresConfig" and a helpful message. Socket.IO and JavaScript plugins should return a clear initialization error or action result until top-level status propagation is supported on those paths.
{
"id": "init-1",
"type": "response",
"status": "requiresConfig",
"message": "Connect your account before starting this plugin.",
"data": null
}
Stdio message protocol
HyperHQ sends one JSON object per line to stdin and expects one JSON object per line on stdout.
Request
{
"id": "msg_123",
"type": "request",
"method": "initialize",
"data": {
"settings": {},
"pluginData": { "id": "my-plugin", "name": "My Plugin", "version": "1.0.0" }
},
"timestamp": 1710000000000
}
Response
{
"id": "msg_123",
"type": "response",
"data": "initialized",
"timestamp": 1710000000100
}
Error
{
"id": "msg_123",
"type": "error",
"error": { "message": "Human-readable error", "recoverable": true },
"timestamp": 1710000000100
}
JavaScript plugin shape
Current JavaScript runner creates a sandbox global named plugin. Define methods on that object; do not use old module.exports hook examples.
plugin.initialize = async (data) => {
plugin._settings = data.settings || {};
return 'initialized';
};
plugin.execute = async (data) => {
return { ok: true, action: data.action || 'default' };
};
plugin.test = async () => true;
plugin.shutdown = async () => 'ok';
Socket.IO events
Socket.IO connects to the root namespace at http://localhost:${HYPERHQ_SOCKET_PORT}. There is no /plugin namespace.
| Event | Direction | Payload/notes |
|---|---|---|
authenticate | Plugin → HyperHQ | First auth: { pluginId, challenge }. Reconnect during grace period: { pluginId, sessionToken }. |
authenticated | HyperHQ → Plugin | { success, pluginId, sessionToken, serverPort, reconnected? }. |
request | HyperHQ → Plugin | Core method request { id, type, method, data, timestamp }. |
response | Plugin → HyperHQ | Core method response; event name is response. |
request_data / requestData | Plugin → HyperHQ | Data request. Both names are accepted. |
data_response / dataResponse | HyperHQ → Plugin | Data response. HyperHQ emits both names for compatibility. |
subscribeEvents | Plugin → HyperHQ | Array of event names to join, e.g. ["gameLaunched"]. |
eventsSubscribed | HyperHQ → Plugin | Subscription acknowledgement. |
hyperHqEvent | HyperHQ → Plugin | Broadcast payload { type, data, timestamp }. |
requestFile | Plugin → HyperHQ | Request a file from the plugin directory. |
fileData | HyperHQ → Plugin | Base64 file payload or error. |
statusUpdate | Plugin → HyperHQ | Optional progress/status for long-running work. |
Socket.IO environment variables
Socket.IO plugins launched by HyperHQ receive:
| Variable | Description |
|---|---|
HYPERHQ_PLUGIN_ID | Manifest ID. |
HYPERHQ_PLUGIN_NAME | Manifest display name. |
HYPERHQ_PLUGIN_VERSION | Manifest version. |
HYPERHQ_SOCKET_PORT | Actual Socket.IO port. Always read this instead of hardcoding. |
HYPERHQ_AUTH_CHALLENGE | One-time challenge for initial authentication. |
PLUGIN_SETTINGS | Launcher-provided compatibility value. Do not treat it as authoritative; use initialize(data).settings. |
The auth challenge is one-time-use and expires after 30 seconds. After a disconnect, HyperHQ allows a 30-second reconnect grace period using the existing session token via authenticate({ pluginId, sessionToken }).
Socket.IO data request methods
Request format:
socket.emit('requestData', {
method: 'getSystems',
params: {},
requestId: 'req-1',
sessionToken
});
socket.on('dataResponse', (response) => {
if (response.requestId === 'req-1' && response.success) {
console.log(response.data);
}
});
| Method | Behavior | Notes |
|---|---|---|
getSystems | Blocking | Returns configured systems. |
getMediaFolders | Blocking | Request media folders. Common params include systemReferenceId/system identifier and mediaTypes. |
getGamesForSystem | Blocking | Accepts flexible system identifiers used by the UI/service layer, such as system ID, reference ID, or name when supported by the frontend handler. |
createSystem | Fire-and-forget | Dispatches system creation to the frontend; returns immediate acknowledgement. |
createEmulator | Fire-and-forget | Dispatches emulator creation. |
addGames | Fire-and-forget | Payload can include a games array with names, filenames/paths, reference IDs, metadata, and enabled state. |
removeGames | Fire-and-forget | Dispatches game removal. Payload aliases are handled by the frontend integration; include stable identifiers where possible (gameIds, referenceIds, filenames/paths). |
launchGame | Fire-and-forget | Dispatches game launch by game identifier. |
Blocking calls wait for a frontend response and time out after 30 seconds. Fire-and-forget calls only confirm that HyperHQ accepted and queued the request.
requestFile constraints
requestFile is intentionally scoped to packaged plugin files.
socket.emit('requestFile', {
requestId: 'asset-1',
filePath: 'assets/schema.json',
sessionToken
});
socket.on('fileData', (msg) => {
if (msg.success) {
const bytes = Buffer.from(msg.data, 'base64');
}
});
Constraints:
sessionTokenis required and strictly validated.- Files must resolve inside the plugin's installation directory.
- Directory traversal and files outside the plugin directory are rejected.
- Maximum file size is 10 MB.
- Response data is base64 encoded.
Actions and onboarding wizards
Plugins can expose UI actions and manifest-driven setup wizards.
{
"actions": [
{
"id": "setup",
"label": "Setup Account",
"description": "Connect and configure this plugin",
"icon": "settings",
"type": "wizard",
"wizard_id": "first-run"
}
],
"onboarding": {
"wizards": [
{
"id": "first-run",
"title": "Set up My Plugin",
"autoStart": "first-run",
"skippable": false,
"steps": [
{ "id": "welcome", "type": "info", "title": "Welcome", "description": "This wizard configures the plugin." },
{ "id": "account", "type": "oauth", "title": "Connect Account", "oauth": { "provider": "example", "settingKey": "account" } },
{ "id": "options", "type": "form", "title": "Options", "form": [ { "key": "sync", "type": "boolean", "label": "Sync on startup", "defaultValue": true } ] }
]
}
]
}
}
Wizard step types: info, oauth, action, async-action, selection-list, and form.
Leaderboard provider plugins
A plugin that provides score/leaderboard data should advertise the leaderboard-provider capability:
{
"capabilities": [
{ "name": "leaderboard-provider", "description": "Provides leaderboards and score submission", "required": false }
]
}
Provider method names routed by HyperHQ:
| Method | Purpose |
|---|---|
leaderboard.getProviderMetadata | Return provider identity and capabilities. |
leaderboard.getLeaderboard | Fetch leaderboard entries for a game/context. |
leaderboard.getScore | Fetch one user's score for a game/context. |
leaderboard.submitScore | Submit a score when supported. |
Marketplace/version metadata
Marketplace records can expose multiple versions per plugin. Version entries include optional channel and platform fields, so a plugin may publish separate stable/beta and platform-specific builds.
Storage locations
- Installed plugin packages are stored under the configured HyperSpin installation's
plugins/folder. - Plugin-owned cached data and downloaded assets should use
_hsm/Plugins/{PluginName}/. - Do not rely on a
%PLUGIN_DATA%magic path in manifests; use real paths provided by your plugin settings or HyperHQ data APIs.