Skip to content

Develop plugins

A plugin is a ZIP containing plugin.json, background scripts and web pages. Plugins call the host’s public capabilities through the SDK, within the permissions players grant.

Terminal window
python tools/retromao.py init plugin --id demo-plugin --name Demo --author YourName
python tools/retromao.py check extensions/demo-plugin
python tools/retromao.py preview extensions/demo-plugin

The terminal shows the preview address. Press Ctrl+C to stop. The preview runs the shipped SDK and your code with local samples and doesn’t connect to real cloud storage.

{
"schemaVersion": 1,
"apiVersion": 1,
"id": "example.hello",
"name": "Plugin example",
"author": "Your name",
"version": "1.0.0",
"minAppVersion": "0.9.96",
"defaultLocale": "zh-Hans",
"description": "Read the library and run a background action.",
"entryPoint": "main.js",
"iconName": "plugin",
"capabilities": ["LIBRARY_READ"],
"pages": [{ "id": "main", "title": "Library", "path": "index.html" }],
"actions": [{ "id": "count", "title": "Count games" }]
}

The author, version and minimum app version are required. Published plugins must include Simplified Chinese. Add other languages with localizations.

In entryPoint, use retromao.define to register functions with the same IDs as actions:

retromao.define({ actions: {
async count() {
let after = null, total = 0;
do {
const page = await retromao.library.records({ after, limit: 200 });
total += page.games.length;
after = page.next;
await retromao.tasks.progress(total);
} while (after);
return { total };
}
} });

Actions run in an isolated JavaScript engine, and each plugin runs one action at a time. Closing a page doesn’t cancel a task. Canceling, disabling or uninstalling does.

pages lists HTML pages in the package. The host injects the SDK, so you don’t need to bundle it. Wait for retromao.ready before calling APIs. You can use a front-end framework you’ve bundled, but you can’t load running code from a CDN.

Pages start actions with retromao.tasks.run(id, args) and follow progress with tasks.watch. The SDK provides theme CSS variables and D-pad focus movement, but you still need to test your layout with touch and controller.

Configuration pages declare kind: "CONFIGURATION" and use a separate HTML file. For simple settings, use settings in the manifest (TEXT, SECRET, BOOLEAN, NUMBER, CHOICE). The host draws the form and stores SECRET values encrypted.

Capability Use
LIBRARY_READ Read the library
LIBRARY_WRITE Edit details, favorites, recent records and media links
NETWORK Reach hosts declared in networkHosts
STORAGE Read and write JSON documents in cloud connections the player allows

Only declare what you need. A * in networkHosts is shown to players as high risk.

API Use
library.game/games/records Read games with keyset paging, up to 200 per page
library.edit/apply Edit details in batches, committed atomically per batch
favorites.list/set, recent.list/edit Favorites and recent records
media.folders/assets/edit Find or link authorized media
index.patch Store fields in the plugin’s namespace, synced with the index
settings.get, data.get/patch Plugin settings and private JSON
http.request Reach declared hosts, with bodies up to 256 KiB
storage.sources/read/write Read and write cloud JSON documents, up to 384 KiB each
tasks.run/get/result/cancel/watch/progress Manage tasks

For every API with examples, see the API reference and type definitions.

Terminal window
python tools/retromao.py pack extensions/demo-plugin --release-tag demo-plugin-1.0.0

Import the ZIP on a device and test installing, permissions, settings, updating and uninstalling. Then follow the publishing steps. Players will find your plugin in Plugins → Store.