Skip to main content

HyperHQ Plugin Developer Guide

This guide shows how to build plugins for the current HyperHQ plugin system.

For full event names and method details, keep the Plugin API Reference and Socket.IO Guide nearby.

What a plugin is

A HyperHQ plugin is a folder under the configured HyperSpin installation's plugins/ directory. HyperHQ discovers plugins by looking for a plugin.json file in each plugin folder.

Plugins can be:

  • executable: a separate process communicating through stdio or Socket.IO.
  • javascript: JavaScript loaded by HyperHQ's sandbox runner.

Minimal folder layout

my-plugin/
├── plugin.json # Required; this is the manifest HyperHQ loads
├── plugin.exe # Executable plugin entry point on Windows
├── plugin # Optional Linux/macOS executable
├── icon.png # Optional icon
└── assets/ # Optional packaged files

Do not name the manifest manifest.json; plugin discovery and validation use plugin.json.

Minimal executable manifest

{
"id": "system-info",
"name": "System Info",
"version": "1.0.0",
"description": "Displays basic system information",
"author": "Your Name",
"type": "executable",
"executable": "plugin.exe",
"executableProviders": {
"windows": "plugin.exe",
"linux": "plugin",
"macos": "plugin"
},
"communication": {
"preferred": "stdio",
"fallback": "socketio",
"stdio": { "enabled": true },
"socketio": { "enabled": true }
},
"capabilities": [
{ "name": "system-info", "description": "Reports system information", "required": true }
],
"permissions": [],
"settings": [
{
"key": "refreshInterval",
"type": "number",
"label": "Refresh Interval (seconds)",
"defaultValue": 5,
"validation": { "min": 1, "max": 60 }
},
{
"key": "showDiskUsage",
"type": "boolean",
"label": "Show Disk Usage",
"defaultValue": true
}
]
}

Important schema details:

  • type is required: executable or javascript.
  • capabilities is required and must not be empty.
  • permissions is an array of { type, scope, description } objects.
  • Settings use label, not name.
  • Current setting types include string, number, boolean, email, password, url, textarea, select, oauth, array, object, file, directory, color, and range.
  • Prefer executableProviders for platform-specific binaries. executableLinux and executableMac remain supported as legacy compatibility fields.

Communication modes

Stdio

Stdio plugins receive one JSON request per line and should print one JSON response per line.

{"id":"1","type":"request","method":"initialize","data":{"settings":{}},"timestamp":1710000000000}

Response:

{"id":"1","type":"response","data":"initialized","timestamp":1710000000100}

Socket.IO

Configure Socket.IO under communication, connect to http://localhost:${HYPERHQ_SOCKET_PORT}, and authenticate with the launch challenge. There is no /plugin namespace.

After authentication, subscribe to broadcasts with subscribeEvents and listen for hyperHqEvent.

Socket.IO plugins receive these environment variables:

  • HYPERHQ_PLUGIN_ID
  • HYPERHQ_PLUGIN_NAME
  • HYPERHQ_PLUGIN_VERSION
  • HYPERHQ_SOCKET_PORT
  • HYPERHQ_AUTH_CHALLENGE
  • PLUGIN_SETTINGS (compatibility only; read current settings from initialize(data).settings)

The auth challenge is one-time-use and expires after 30 seconds. Reconnect within the 30-second grace period with authenticate({ pluginId, sessionToken }).

See Plugin Socket.IO Guide for complete examples.

Required methods

Every plugin must support:

MethodRequiredDescription
initialize(data)YesReceive settings and plugin metadata.
execute(data)YesRun an action or the main plugin operation.
test(data)YesHealth check.
shutdown(data)OptionalCleanup before stopping.

For stdio plugins that emit a complete PluginResponse, startup setup can be reported with top-level status: "requiresConfig" and a clear message. Socket.IO and JavaScript plugins should return a clear initialization error/action result until top-level status propagation is supported on those paths.

{
"id": "init-1",
"type": "response",
"status": "requiresConfig",
"message": "Connect your account before using this plugin.",
"data": null
}

Python stdio example

#!/usr/bin/env python3
import json
import platform
import sys
import time

class Plugin:
def __init__(self):
self.settings = {}

def initialize(self, data):
self.settings = data.get('settings', {})
return 'initialized'

def execute(self, data):
return {
'system': platform.system(),
'release': platform.release(),
'action': data.get('action', 'get_info')
}

def test(self, data):
return True

def shutdown(self, data):
return 'ok'

plugin = Plugin()

for line in sys.stdin:
if not line.strip():
continue
message = json.loads(line)
method = message.get('method')
request_id = message.get('id')
data = message.get('data') or {}

try:
if method == 'initialize':
result = plugin.initialize(data)
elif method == 'execute':
result = plugin.execute(data)
elif method == 'test':
result = plugin.test(data)
elif method == 'shutdown':
result = plugin.shutdown(data)
else:
raise ValueError(f'Unknown method: {method}')

print(json.dumps({
'id': request_id,
'type': 'response',
'data': result,
'timestamp': int(time.time() * 1000)
}), flush=True)
except Exception as exc:
print(json.dumps({
'id': request_id,
'type': 'error',
'error': {'message': str(exc), 'recoverable': True},
'timestamp': int(time.time() * 1000)
}), flush=True)

JavaScript plugin example

The current JavaScript runner creates a sandbox global named plugin. Define plugin.initialize, plugin.execute, plugin.test, and optionally plugin.shutdown. Do not use old module.exports, onLoad, or hook-style examples.

plugin.json:

{
"id": "js-hello",
"name": "JS Hello",
"version": "1.0.0",
"description": "JavaScript plugin example",
"author": "Your Name",
"type": "javascript",
"main": "plugin.js",
"capabilities": [
{ "name": "hello", "description": "Says hello", "required": true }
],
"permissions": []
}

plugin.js:

plugin.initialize = async (data) => {
plugin._settings = data.settings || {};
return 'initialized';
};

plugin.execute = async (data) => {
return { message: `Hello ${data.name || 'HyperHQ'}` };
};

plugin.test = async () => true;

plugin.shutdown = async () => 'ok';

Settings, OAuth, and onboarding

Settings are top-level manifest entries. HyperHQ renders current setting types including OAuth, structured fields, and path pickers.

Path picker settings:

  • Use type: "file" for file paths.
  • Use type: "directory" for folder paths.
  • Add browse: "file" or browse: "directory" to a string setting when users need editable text plus a path picker.
  • Use browse: { "type": "directory", "buttonLabel": "Choose Folder" } to set the picker mode and button text.
  • Use validation.fileTypes to filter file pickers.
{
"settings": [
{
"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": "libraryRoots",
"type": "array",
"label": "Library Roots",
"validation": { "itemType": "string", "minItems": 1 }
},
{
"key": "romFolder",
"type": "string",
"label": "ROM Folder",
"browse": "directory",
"defaultValue": "",
"description": "Folder containing ROM files"
},
{
"key": "emulatorExe",
"type": "file",
"label": "Emulator Executable",
"validation": {
"fileTypes": [
{ "name": "Executables", "extensions": ["exe"] }
]
}
}
]
}

Onboarding form steps use the same setting shape, including file, directory, browse, and validation.fileTypes.

Use manifest-driven onboarding wizards to guide first-run setup:

{
"actions": [
{
"id": "setup",
"label": "Setup",
"description": "Configure this plugin",
"icon": "settings",
"type": "wizard",
"wizard_id": "first-run"
}
],
"onboarding": {
"wizards": [
{
"id": "first-run",
"title": "Set up the plugin",
"autoStart": "first-run",
"skippable": false,
"steps": [
{ "id": "intro", "type": "info", "title": "Welcome", "description": "Let's configure this plugin." },
{ "id": "login", "type": "oauth", "title": "Connect Account", "oauth": { "provider": "example", "settingKey": "account" } },
{ "id": "options", "type": "form", "title": "Options", "form": [ { "key": "enabled", "type": "boolean", "label": "Enable sync", "defaultValue": true } ] }
]
}
]
}
}

Wizard step types are info, oauth, action, async-action, selection-list, and form.

Calling HyperHQ from Socket.IO plugins

Data requests are sent with requestData or request_data; HyperHQ replies with both dataResponse and data_response for compatibility.

socket.emit('requestData', {
method: 'getSystems',
params: {},
requestId: 'systems-1',
sessionToken
});

Supported data methods:

MethodBehavior
getSystemsBlocking.
getMediaFoldersBlocking.
getGamesForSystemBlocking; system identifier can be ID/reference/name where frontend supports it.
createSystemFire-and-forget.
createEmulatorFire-and-forget.
addGamesFire-and-forget; accepts broader game metadata payloads.
removeGamesFire-and-forget; removal aliases are handled by the frontend integration.
launchGameFire-and-forget.

Packaged file access

Socket.IO plugins can use requestFile to read packaged files from their own plugin directory. The request must include the session token, the resolved path must remain inside the plugin directory, and the file must be 10 MB or smaller.

Storage and installation

  • Installed plugins live under the configured HyperSpin installation's plugins/ folder.
  • Plugin cached data and downloaded assets should use _hsm/Plugins/{PluginName}/.
  • Do not document or rely on a %PLUGIN_DATA% magic path; use real paths from settings or HyperHQ APIs.

Leaderboard provider plugins

To provide leaderboard/score functionality, advertise:

{
"capabilities": [
{ "name": "leaderboard-provider", "description": "Provides leaderboard data", "required": false }
]
}

Implement these provider methods when applicable:

  • leaderboard.getProviderMetadata
  • leaderboard.getLeaderboard
  • leaderboard.getScore
  • leaderboard.submitScore

Packaging and marketplace notes

Package plugins as ZIP files containing plugin.json, runtime files, icons, and docs. Marketplace version records can be channel- and platform-specific, so plan release artifacts accordingly (for example stable Windows and beta Linux builds as separate versions).

There are no checked-in starter template ZIPs in this docs folder. Treat examples in these docs as conceptual starting points rather than download links.

Troubleshooting and debugging

  • Log incoming request methods and outgoing response IDs while developing.
  • Never log OAuth tokens, API keys, auth challenges, or session tokens.
  • Confirm plugin.json is named correctly and contains required type, capabilities, and permissions fields.
  • For executable plugins, confirm platform executable paths and file permissions.
  • For stdio plugins, write only JSON response lines to stdout. Send debug logs to stderr.
  • For Socket.IO plugins, connect to the root namespace, read HYPERHQ_SOCKET_PORT, and authenticate with HYPERHQ_AUTH_CHALLENGE.
  • For Socket.IO responses, echo the incoming request id exactly.
  • Return useful errors from initialize when settings are missing, especially for OAuth and onboarding flows.

Manual test commands

For stdio plugins:

printf '%s\n' '{"id":"1","type":"request","method":"initialize","data":{"settings":{}},"timestamp":0}' | ./plugin
printf '%s\n' '{"id":"2","type":"request","method":"execute","data":{"action":"test"},"timestamp":0}' | ./plugin
printf '%s\n' '{"id":"3","type":"request","method":"test","data":{},"timestamp":0}' | ./plugin

For Socket.IO plugins, launch through HyperHQ so the required port, challenge, and settings environment variables are present.