Appearance
Plugins Module
This module allows you to manage and execute Desktopr plugins loaded in your app.
Desktopr plugins are WebAssembly (WASM) modules executed by Desktopr inside a native Tauri + Wasmtime + WASI runtime. They are useful for adding local processing, data utilities, file conversion, validation, computations, and other advanced features without rebuilding the whole desktop app.
How to create a new plugin
You can find the guide to develop a plugin here.
Overview
Desktopr plugins do not run inside the browser page and do not run inside a browser Web Worker.
They are executed by the Rust side of the Desktopr wrapper through Wasmtime and WASI.
The runtime provides:
- Native WASI execution
- WASM module validation
- JSON-based stdin/stdout communication
- Captured stderr diagnostics
- Persistent per-plugin storage
- Sandboxed environment
- Automatic timeout handling
- Cross-platform consistency on macOS, Windows, and Linux
Plugins are managed via the JavaScript bridge at Desktopr.plugins.
Architecture
The system is composed of three layers:
| Layer | Language | Responsibility |
|---|---|---|
| Frontend | JavaScript or TypeScript | Calls plugins through the Desktopr Bridge API |
| Desktopr Wrapper | Rust + Tauri | Manages plugin installation, WASM validation, execution, timeout, stdin/stdout/stderr, and storage |
| Execution Runtime | Wasmtime + WASI | Executes the loaded WebAssembly module in a sandboxed native runtime |
The plugin itself is a .wasm file compiled for the WASI target.
API Reference
| Method | Parameters | Returns | Description |
|---|---|---|---|
| call(modulePath, payload, timeoutMs?) | modulePath: string – The module filename, for example "math.wasm".payload: PluginCallPayload – Object indicating the function to run and its arguments.timeoutMs?: number – Optional execution timeout in milliseconds. | Promise<PluginCallResult> | Executes a function inside a loaded WASM plugin. |
| status() | – | Promise<PluginStatusResult> | Returns the current native plugin runtime status. |
| modules.list() | – | Promise<string[]> | Lists all installed WASM modules available to the plugin runtime. |
| modules.remove(name) | name: string – Module filename to delete. | Promise<boolean> | Removes a module from the installed modules directory. |
| modules.addFromBytes(name, contents) | name: string – Name for the new module.contents: Uint8Array – Raw WASM bytes to store. | Promise<any> | Adds a module directly from memory bytes. |
| modules.add(name, maxBytes?) | name: string – Filename to assign.maxBytes?: number – Optional file size limit. | Promise<PluginAddResult> | Opens a native file picker to select and store a .wasm file. |
| killJobs() | - | Promise<boolean> | Kills all running plugin runtimes. |
Each method is asynchronous and returns a Promise.
Types
PluginCallPayload
| Field | Type | Description |
|---|---|---|
| fn | string | The function name to execute inside the WASM module. |
| args | array or object | Arguments passed to the target function. Both positional and named arguments are supported. |
| [key: string] | any | Optional additional fields included in the JSON payload sent to the plugin. |
{
fn: string;
args?: unknown[] | Record<string, unknown>;
[key: string]: unknown;
};PluginCallResult
A successful Desktopr plugin call response may look like this:
{
durationMs: number;
error: string|null|undefined;
id: string;
ok: boolean;
stderr: string;
stdout: string;
value: {
error?: string,
value?: string|number,
ok: boolean
};
};Notes:
stdoutcontains the raw text printed by the plugin to stdout.stderrcontains debug or diagnostic output printed by the plugin to stderr.valueis Desktopr's parsed JSON value fromstdout, when parsing succeeds.errorcontains a runtime-level error if the call failed before or during execution.
PluginAddResult
{
bytes: number;
name: string;
path: string;
saved: boolean;
};PluginStatusResult
{
ready: true,
runtime: string
};Input/Output Standard
Desktopr plugins communicate through standard execution streams.
stdinreceives the JSON request.stdoutmust contain the final JSON response.stderrmay contain debug logs and diagnostic messages.
Input: Bridge → Plugin
The bridge serializes the payload as JSON and sends it to the plugin through stdin.
Example:
js
await Desktopr.plugins.call("math.wasm", {
fn: "add",
args: [12, 2]
});The plugin receives:
json
{ "fn": "divide", "args": [12, 2] }Output: Plugin → Bridge
The plugin must write a JSON response to stdout.
Success:
json
{"ok":true,"value":6}Error:
json
{"ok":false,"error":"division by zero"}MANDATORY FORMAT
The final plugin result must be valid JSON printed to stdout.
Extra non-JSON output on stdout may cause the result parsing to fail.
Module Lifecycle Example
1. Add a WASM module
js
await Desktopr.plugins.add("math");This opens a native file picker and stores the selected .wasm module in:
text
_external_modules/math.wasm2. List installed plugins
js
const plugins = await Desktopr.plugins.list();
console.log(plugins);3. Execute a function
js
const result = await Desktopr.plugins.call("math.wasm", {
fn: "divide",
args: [12, 2]
});Example response:
json
{
"id": "plugin_req2",
"ok": true,
"stdout": "{\"ok\":true,\"value\":6}",
"stderr": "",
"value": {
"ok": true,
"value": 6
},
"error": null,
"durationMs": 24
}Access a plugin storage with the Desktopr File System module
Desktopr can expose a plugin-scoped filesystem API through the bridge.
Conceptually:
ts
const pfs = await Desktopr.fs.pluginFs("math.wasm");
await pfs.writeText("config.json", "{}");
const config = await pfs.readText("config.json");This targets:
text
app_data_dir/.desktopr/.main/_external_modules_storage/math.wasm/config.jsonThe plugin itself can access the same file with:
rs
std::fs::read_to_string("config.json")?;Delete behavior
Plugin storage is technical storage owned by the plugin.
When using the plugin filesystem scope, remove operations delete files directly and do not use the data trash system.
Security and Sandbox
Desktopr plugin execution is sandboxed through the WASI runtime.
The runtime provides:
- no network access
- no access to arbitrary user files
- no access to the full app filesystem
- access only to directories explicitly provided by Desktopr
- timeout-based interruption for long-running jobs
- stdout/stderr capture limits
- WASM validation before execution
WARNING
Avoid infinite loops or blocking operations inside your WASM module. Long-running executions may be interrupted by the runtime timeout.
Load a Plugin Dynamically
If you want to load a module dynamically without opening the file picker, use addFromBytes:
ts
await Desktopr.plugins.addFromBytes("my-plugin.wasm", wasmBytes);wasmBytes must be a Uint8Array containing the raw bytes of the plugin WASM module.
Keywords Reference
| Term | Meaning |
|---|---|
| Plugin | A WASM module executed by Desktopr |
| WASM | WebAssembly binary module |
| WASI | System interface used by the WASM module |
| Wasmtime | Native runtime used by Desktopr to execute WASM |
| Persistent plugin storage | Per-plugin storage under _external_modules_storage/<plugin>.wasm/ |
| stdin | JSON input sent from Desktopr to the plugin |
| stdout | Final JSON response printed by the plugin |
| stderr | Debug/diagnostic output captured by Desktopr |
WebAssembly
WebAssembly (WASM) is a portable binary format that enables high-performance code written in languages such as Rust, C, or C++ to run inside sandboxed runtimes.
Desktopr uses WASM plugins to run local, isolated, cross-platform logic inside the desktop wrapper.
New Plugin
You can find the guide to develop a plugin here.
Notes
DEVELOPMENT NOTES
- Use
wasm32-wasip1for Rust plugins. - Do not use network access; it is blocked by the runtime.
- Use relative filesystem paths for persistent plugin files.
- Do not print extra output to
stdout. - The final JSON response must be printed to
stdout. - Use
stderronly for short debug logs and diagnostics. - Large outputs should be written to plugin storage, not to
stdoutorstderr. - All modules must follow the JSON input/output protocol documented above.