Plugin development
Architecture
A GameAP plugin is a WASM module for the wasm32-wasip1 target (WASI preview 1), built as a reactor. The panel runs each plugin in an isolated wazero runtime: no file system, no network, no environment variables; stdout and stderr are discarded.
The panel and the plugin exchange protobuf messages through WASM linear memory: the panel writes the request into guest memory using the malloc exported by the plugin, calls the function and reads the response. The Plugin API version is 1.
Message types and SDKs are published from the github.com/gameap/gameap-proto repository:
gameap-plugin-sdk(crates.io) — the full Rust SDK: the ABI layer, thePlugintrait, theregister_plugin!macro, typed host-function clients;@gameap/proto-as(npm) — AssemblyScript message types (without the ABI layer);@gameap/proto(npm) — TypeScript types (for tooling; building a WASM plugin in plain JS is not currently possible).
The plugin interface
A plugin implements the PluginService service from plugin.proto. The service methods:
| Method | Purpose |
|---|---|
GetInfo |
Returns the plugin metadata (PluginInfo) |
Initialize |
Initialization when the plugin is loaded |
Shutdown |
Shutdown when the plugin is unloaded |
HandleEvent |
Handling a panel event |
GetSubscribedEvents |
The list of event types the plugin subscribes to |
GetHTTPRoutes |
The list of the plugin’s HTTP routes |
HandleHTTPRequest |
Handling an HTTP request on a plugin route |
GetFrontendBundle |
The embedded frontend: JS bundle and CSS (optional) |
GetServerAbilities |
Registering the plugin’s server permissions (optional) |
In the Rust SDK the interface is represented by the Plugin trait with neutral default implementations — in practice only get_info is mandatory. The register_plugin! macro generates all the required WASM exports, including malloc/free and the API version check.
PluginInfo metadata
| Field | Description |
|---|---|
id |
The plugin’s string identifier (see the requirements below) |
name |
Plugin name |
version |
Version (semver) |
description |
Short description |
author |
Author |
license |
License (for example, MIT) |
homepage |
Link to the plugin page |
required_permissions |
Declared permissions (not checked in the current version of the panel) |
api_version |
Plugin API version, must be "1" |
Requirements for id. Use a stable identifier made of base32 alphabet characters a-z2-7, without hyphens. The panel normalizes the id: a string with hyphens or other characters is replaced by a hash, which breaks the /api/plugins/{id}/... and /plugins/{id}/... paths. Avoid purely numeric ids as well: such an identifier is treated as a decimal numeric ID (id parsing first tries to parse the string as a number). Examples of valid ids from real plugins: hexeditor4jm2, ezvdsxmlu6fbk, dshdabjp2l73a.
Events
A plugin subscribes to panel events with the GetSubscribedEvents method. The event types (listed without the EVENT_TYPE_ prefix):
| Event | Cancellable | Delivery |
|---|---|---|
SERVER_PRE_START, SERVER_PRE_STOP, SERVER_PRE_RESTART, SERVER_PRE_INSTALL, SERVER_PRE_UPDATE, SERVER_PRE_REINSTALL, SERVER_PRE_DELETE |
Yes | Synchronous |
SERVER_POST_START, SERVER_POST_STOP, SERVER_POST_RESTART, SERVER_POST_INSTALL, SERVER_POST_UPDATE, SERVER_POST_REINSTALL, SERVER_POST_DELETE |
No | Asynchronous |
SERVER_CREATED, SERVER_UPDATED, SERVER_DELETED |
No | Asynchronous |
DAEMON_TASK_CREATED, DAEMON_TASK_COMPLETED, DAEMON_TASK_FAILED |
No | Asynchronous |
Pre-events are delivered synchronously and block the operation: a plugin can cancel it by returning an EventResult with should_cancel = true and a message, or modify the data via modified_data. Post-events and the other types are delivered asynchronously and do not affect the operation.
A server event contains a full snapshot of the server (ServerEventPayload); a task event contains the daemon task data (TaskEventPayload). The event handler timeout is 10 seconds; once it expires the plugin is disabled until the panel is restarted.
Plugin HTTP routes
A plugin registers HTTP routes with the GetHTTPRoutes method; each route is an HTTPRoute message:
| Field | Description |
|---|---|
path |
The path, starts with /; path parameters of the form {name} are supported |
methods |
Methods: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS |
requires_auth |
Require an authenticated user |
admin_only |
Require an administrator |
description |
Route description |
Routes are available at /api/plugins/{plugin_id}/.... The request passed to the plugin contains the method, the relative path, the headers, path and query parameters, the body (no larger than 1 MB) and the user session (if the request is authenticated). The plugin returns an HTTPResponse with the status_code, headers and body fields.
Host functions
All calls from a plugin to the panel and the outside world go through host functions, grouped into gameap-* modules:
| Module | Functions | Purpose |
|---|---|---|
gameap-log |
log |
Writing to the panel log |
gameap-cache |
get, set, delete |
The panel’s shared cache (keys prefixed with plugin:) |
gameap-crypto |
random_uint64, random_string, argon2_hash, argon2_verify |
Random value generators, Argon2 hashing |
gameap-http |
fetch |
Outbound HTTP requests with SSRF protection |
gameap-storage |
get, set, delete, list |
The plugin’s persistent key-value storage |
gameap-servercontrol |
start_server, stop_server, restart_server, update_server, install_server, reinstall_server |
Game server control (returns a task_id) |
gameap-servers |
find_servers, get_server, save_server, delete_server |
Game servers |
gameap-users |
find_users, get_user |
Panel users |
gameap-nodes |
find_nodes, get_node |
Dedicated servers (nodes) |
gameap-games |
find_games, get_game |
Games |
gameap-gamemods |
find_game_mods, get_game_mod |
Game mods |
gameap-daemontasks |
find_daemon_tasks, create_daemon_task |
Daemon tasks |
gameap-serversettings |
find_server_settings, save_server_setting |
Game server settings |
gameap-nodefs |
read_dir, mk_dir, copy, move, download, upload, remove, get_file_info, chmod |
File operations on a node |
gameap-nodecmd |
execute_command |
Executing a command on a node |
Details:
gameap-httpproxies requests through the panel with SSRF protection: by default only thehttpsscheme is allowed, private and service IPs are blocked, and the response body is limited to 10 MB. The policy is configured with thePLUGIN_HTTP_*environment variables (see Installation and management).gameap-storageis the plugin’s persistent storage, isolated byplugin_id, with optional binding of records to an entity (entity_type,entity_id). Use it for plugin settings: the separate configuration mechanism (configinInitializeRequest) is not used in the current version of the panel.gameap-nodefsandgameap-nodecmdwork with files and commands on the dedicated server (node) through GameAP Daemon.
Important: host functions run with the panel’s own privileges, without additional checks. That is why installing plugins is entrusted to administrators only — install plugins only from sources you trust.
Runtime limits
| Limit | Value |
|---|---|
| Concurrent calls to one plugin | 1 (calls are serialized) |
| Plugin call timeout | 30 s (module start — 60 s) |
| Event handler timeout | 10 s |
Size of an uploaded .wasm file |
100 MB |
| Body of an HTTP request to a plugin | 1 MB |
Body of a gameap-http response |
10 MB |
When a call timeout is exceeded, the panel disables the plugin until a restart. The file system and the network are not available from WASM — only through host functions.
Building a plugin with Rust
The structure of a minimal project (following plugin-hex-editor):
rust-toolchain.toml:
[toolchain]
channel = "1.94.0"
targets = ["wasm32-wasip1"]
Cargo.toml:
[lib]
crate-type = ["cdylib"]
[dependencies]
gameap-plugin-sdk = "0.1"
[profile.release]
opt-level = "z"
lto = true
strip = true
A minimal src/lib.rs — a plugin that only returns metadata and an embedded frontend:
#![cfg(target_arch = "wasm32")]
use gameap_plugin_sdk::proto::gameap::plugin as pb;
use gameap_plugin_sdk::{Plugin, PluginError, register_plugin};
const FRONTEND_JS: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/plugin.js"));
const FRONTEND_CSS: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/plugin.css"));
#[derive(Default)]
struct MyPlugin;
impl Plugin for MyPlugin {
fn get_info(&mut self, _req: pb::GetInfoRequest) -> Result<pb::PluginInfo, PluginError> {
Ok(pb::PluginInfo {
id: "myplugin2j7d".into(),
name: "My Plugin".into(),
version: env!("CARGO_PKG_VERSION").into(),
description: "My first GameAP plugin".into(),
author: "Me".into(),
api_version: "1".into(),
..Default::default()
})
}
fn get_frontend_bundle(
&mut self,
_req: pb::GetFrontendBundleRequest,
) -> Result<pb::GetFrontendBundleResponse, PluginError> {
Ok(pb::GetFrontendBundleResponse {
bundle: FRONTEND_JS.to_vec(),
has_bundle: !FRONTEND_JS.is_empty(),
styles: FRONTEND_CSS.to_vec(),
has_styles: !FRONTEND_CSS.is_empty(),
})
}
}
register_plugin!(MyPlugin);
Building:
cargo build --target wasm32-wasip1 --release
# optional — size reduction (binaryen):
wasm-opt -Oz target/wasm32-wasip1/release/my_plugin.wasm -o my-plugin.wasm
The resulting .wasm file is installed through the panel interface or copied into the plugins/ directory (see Installation and management). The frontend is built separately and embedded into the .wasm (see Plugin frontend).
Development in AssemblyScript
There is no ready-made SDK for AssemblyScript yet: the @gameap/proto-as package provides only message types, and the ABI layer (packing pointers, error handling, exporting malloc/free) has to be written by hand. Because of AssemblyScript’s garbage collector, buffers passed to the host must be pinned (__pin/__unpin), and a JSON parser and basic utilities have to be implemented yourself or pulled in from third-party libraries. A working AssemblyScript plugin example is plugin-minecraft-modrinth.
Validating a plugin before installation
The panel can validate a .wasm file without installing it — the POST /api/admin/plugins/upload/dry-run endpoint (multipart, file field). The response returns the plugin metadata, HTTP routes, server permissions, event subscriptions, whether a frontend is present, and a list of errors. The same check runs in the interface when a file is uploaded.
Plugin examples
- plugin-hex-editor — a minimal Rust plugin: metadata plus an embedded frontend, no host functions.
- plugin-goldsrc-addons — a Rust plugin with HTTP routes, file operations on a node (
nodefs), command execution (nodecmd) and panel API calls (RCON) from the frontend. - plugin-minecraft-modrinth — an AssemblyScript plugin: calls to an external API (modrinth.com) through
gameap-http, caching, persistent storage.