Skip to main content

Plugin System Architecture

HyperHQ supports a hybrid plugin system with JavaScript and executable plugins.

Runtime overview

Plugins extend HyperHQ without modifying core code. They can add setup flows, integrations, actions, data providers, hardware helpers, and background services.

Plugin typeEntry pointCommunication
javascriptmain (.js/.mjs, defaults to index.js)In-process sandbox runner.
executableexecutable / executableProvidersStdio JSON lines or Socket.IO.

HyperHQ discovers installed plugins by scanning the configured HyperSpin install plugins/ folder and loading folders that contain plugin.json. Plugin docs and packages should not use manifest.json for plugin manifests.

Main services

ServicePurpose
PluginManagerServiceRegistry, discovery, lifecycle, start/stop, settings refresh.
PluginOperationServiceInstall, uninstall, update operations.
PluginValidationServiceManifest and package validation.
PluginSettingsServicePer-plugin settings persistence.
PluginOnboardingService / launcherManifest-driven setup wizards.
PluginOAuthSessionServiceOAuth setting session state and artifacts.
ExecutablePluginRunnerRenderer-side facade for executable plugin messaging.
JavaScriptPluginRunnerJavaScript sandbox runner.
Main-process PluginLauncherSpawns executable plugins and chooses stdio vs Socket.IO.
Main-process Socket.IO serverAuthenticated realtime plugin transport and data bridge.

Manifest schema summary

Every plugin requires plugin.json.

{
"id": "my-plugin",
"name": "My Plugin",
"version": "1.0.0",
"description": "What this plugin does",
"author": "Author Name",
"type": "executable",
"executable": "plugin.exe",
"executableProviders": {
"windows": "plugin.exe",
"linux": "plugin",
"macos": "plugin"
},
"communication": {
"preferred": "socketio",
"fallback": "stdio",
"stdio": { "enabled": true },
"socketio": { "enabled": true }
},
"capabilities": [
{ "name": "realtime", "description": "Uses realtime plugin events", "required": true }
],
"permissions": [
{ "type": "network", "scope": "localhost", "description": "Connect to HyperHQ Socket.IO" }
],
"settings": [
{ "key": "enabled", "type": "boolean", "label": "Enabled", "defaultValue": true },
{ "key": "romFolder", "type": "directory", "label": "ROM Folder", "defaultValue": "" },
{ "key": "installPath", "type": "string", "label": "Install Path", "browse": "directory" }
]
}

Important current behavior:

  • id, name, version, description, author, type, and non-empty capabilities are required.
  • type must be javascript or executable.
  • permissions is an array. Valid permission types are file, network, system, and database.
  • Settings use label instead of name.
  • Supported setting types include string, number, boolean, email, password, url, textarea, select, oauth, array, object, file, directory, color, and range.
  • file and directory settings render a Browse button. string settings render the same picker when browse is set to file, directory, true, or an object with type and optional buttonLabel.
  • Platform executable resolution prefers executableProviders, then legacy executableLinux/executableMac, then executable.

Plugin locations and storage

  • Installed plugin packages: configured HyperSpin installation plugins/ folder.
  • Plugin-owned cached data/assets: _hsm/Plugins/{PluginName}/.
  • Do not claim a %PLUGIN_DATA% manifest magic variable unless code support is added. Use real paths from settings or HyperHQ APIs.

Lifecycle

Discover plugin.json → Validate → Register → Start → Initialize → Active
│ │
└─ Requires config ◄──┘

For stdio plugins that emit a complete PluginResponse, initialize may return top-level status: "requiresConfig" with a message. HyperHQ marks the plugin requires_config, emits a plugin requires-config event, and surfaces setup to the UI. Socket.IO and JavaScript paths currently wrap method return values, so use a clear initialization error/action result there until top-level status propagation is implemented.

JavaScript plugins

The current JavaScript runner creates a sandbox global named plugin and validates these methods:

plugin.initialize = async (data) => 'initialized';
plugin.execute = async (data) => ({ success: true });
plugin.test = async () => true;
plugin.shutdown = async () => 'ok'; // optional

Old module.exports = { onLoad, onGameLaunch, onUnload } style examples do not match the current runner.

Executable plugins

Executable plugins support two transports.

Stdio

HyperHQ sends JSON-line requests on stdin and expects JSON-line responses on stdout.

Socket.IO

Socket.IO configuration belongs under communication, not as a top-level socketio object.

Socket.IO plugins are launched with:

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

Plugins connect to the root namespace at http://localhost:${HYPERHQ_SOCKET_PORT}. There is no /plugin namespace.

Authentication uses authenticate:

  • Initial: { pluginId, challenge } with the one-time 30-second challenge.
  • Reconnect: { pluginId, sessionToken } within the 30-second reconnect grace period.

Core method calls use request from HyperHQ and response from the plugin.

Socket.IO data bridge

Data requests are sent by plugins on requestData or request_data. HyperHQ emits both dataResponse and data_response replies for compatibility.

MethodBehavior
getSystemsBlocking, waits for frontend response.
getMediaFoldersBlocking.
getGamesForSystemBlocking; frontend handler supports flexible system identifiers where available.
createSystemFire-and-forget.
createEmulatorFire-and-forget.
addGamesFire-and-forget with broad game payload support.
removeGamesFire-and-forget; frontend integration handles common removal aliases.
launchGameFire-and-forget.

Blocking data requests time out after 30 seconds. Fire-and-forget requests return an immediate acknowledgement after dispatch.

Event subscriptions

Plugins subscribe with subscribeEvents and receive broadcasts on hyperHqEvent.

socket.emit('subscribeEvents', ['gameLaunched', 'gameClosed']);
socket.on('hyperHqEvent', ({ type, data, timestamp }) => {});

Packaged file requests

Socket.IO plugins can request packaged files with requestFile. The request must include the current session token, resolve inside the plugin directory, and target a file no larger than 10 MB. HyperHQ responds on fileData with base64 data or an error.

Actions, onboarding, and OAuth

Manifests can define actions and onboarding wizards. Action type wizard can launch a wizard via wizard_id. Wizard step types are info, oauth, action, async-action, selection-list, and form.

Wizard form steps use the same setting definition shape as top-level settings, including path picker fields.

OAuth settings (type: "oauth") and OAuth wizard steps are handled by HyperHQ's OAuth session service. Expired/invalid OAuth state should be surfaced as a configuration problem so users can reconnect; stdio/full-response plugins can use requiresConfig, while other transports should return a clear recoverable initialization/action error.

Leaderboard provider capability

Leaderboard provider plugins advertise:

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

Provider method names routed by HyperHQ:

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

Marketplace/version metadata

Marketplace plugin versions can include channel and platform, allowing platform-specific or channel-specific releases.