How to Develop Plugins for Motrix Using the Plugin SDK: A Complete Guide
Motrix treats plugins as self-contained ES2020 modules that run inside a QuickJS sandbox, orchestrated by the Plugin Registry, Plugin Host, and Capability Bridge, which you can build and package using the official @motrix/plugin-sdk toolchain.
Motrix is a full-featured, open-source download manager that supports secure extensibility through a sandboxed plugin system. To develop plugins for Motrix using the Plugin SDK, you create isolated modules that interact with the application via a controlled Runtime API rather than direct system access. This guide examines the internal architecture, lifecycle hooks, and development workflow based on the actual implementation in the agalwood/Motrix repository.
Understanding the Motrix Plugin Architecture
The plugin system relies on three coordinated components that manage discovery, execution, and API bridging.
The Three Core Components
Plugin Registry — Located in src/core/plugin/plugin-registry.ts, this component scans the plugins/ (community) and builtin-plugins/ (built-in) directories, parses each motrix-plugin.json manifest, resolves internationalization strings, and maintains an in-memory index of available plugins via the byId map.
Plugin Host — Implemented in src/core/plugin/host/plugin-host.ts, this manages worker threads for active plugins, validates permissions through ffmpegSatisfies checks, verifies built-in signatures via verifyBuiltinSignature, and instantiates the sandbox environment.
Capability Bridge — Embedded within the Plugin Host, this injects the motrix runtime object into the QuickJS sandbox, forwarding hook invocations and locale changes between the main process and the isolated plugin context.
Plugin Structure and Manifest Requirements
Every Motrix plugin follows a strict package layout to ensure the registry can parse and load it correctly.
File Structure
my-plugin/
├─ src/ # TypeScript/JavaScript source
│ └─ index.ts
├─ motrix-plugin.json # Manifest (required)
└─ package.json
The motrix-plugin.json Manifest
The registry loads this file using parseManifest from src/core/plugin/manifest/parse.ts. It declares the plugin's id, version, permissions, activationEvents, and the main entry point. Internationalization placeholders are resolved at load time by resolveManifestI18n in src/core/plugin/manifest/i18n-resolve.ts, which merges locale JSON files from the l10n/ directory into the manifest.
Sandbox Constraints
Plugins operate without Node.js built-ins and cannot access the filesystem or network directly. All external interactions must be declared as capabilities (e.g., ffmpeg, storage, network) and are gated by the host before activation.
Development Workflow with the Plugin SDK
The official SDK provides a complete toolchain for scaffolding, building, and packaging extensions.
Creating a Scaffold
Run the following command to generate a starter repository with a valid TypeScript configuration and sample hook:
npx create-motrix-plugin my-plugin
Development and Build Process
Use the watch mode to compile changes into a single ES2020 bundle:
npm run dev
# or
pnpm dev
This outputs to dist/plugin.js, which the SDK validates against the @motrix/plugin-manifest-schema.
Packaging and Installation
Package your plugin into a signed .moext file (a tarball) for distribution:
pnpm exec motrix-plugin pack
Install the resulting plugin-name-version.moext through the Motrix UI via Plugins → Install from file, or drop it into the plugins/ directory manually. The PluginRegistry will detect it on the next discovery scan.
How Motrix Loads and Activates Plugins
The lifecycle from disk to execution follows a strict sequence defined in the core source files.
Discovery and Registration
When Motrix starts, PluginRegistry.performDiscover() reads every subdirectory under the user data plugins/ folder. It invokes parseManifest on each motrix-plugin.json and populates the registry's internal index.
Internationalization Resolution
The registry calls loadLocale to fetch locale JSON files, then uses resolveManifestI18n to merge translations into the manifest object for display in the UI.
Activation and Worker Initialization
When a plugin is enabled, PluginHost.activate(id) executes the following steps:
- Validation: Checks that the plugin is enabled and that the active plugin cap is not exceeded.
- Capability Check: Performs optional
ffmpegversion validation viaffmpegSatisfies. - Bundle Loading: Reads
bundle.moextfor built-ins ordist/plugin.jsfor community plugins, verifying signatures for built-in packages. - Bridge Creation: Instantiates a
CapabilityBridge, passing the sandbox the manifest, bundle source, and effective permissions calculated asrequired ∪ (optional ∩ granted).
Hook Invocation and Lifecycle Management
Plugins register hooks via motrix.hooks.register('resolve', handler). When Motrix needs to trigger a hook, it calls PluginHost.invokeHook(id, hookName, …), which marshals data into the worker thread and returns the result.
If a plugin remains idle for idleDisposeMs (default 5 minutes), or during application shutdown, PluginHost.deactivate(id) tears down the worker, executes any onDeactivate handlers with a strict time budget, and updates the PluginStateStore.
Understanding Permissions and Grants
The permission system distinguishes between mandatory and optional capabilities.
Required vs. Optional Permissions
Required permissions listed in the permissions array of motrix-plugin.json are always granted at activation. Optional permissions declared under optionalPermissions require explicit user approval and are managed by an optional GrantsManager wired into PluginHost via opts.pluginGrants.
The Grants System
The host resolves the effective permission set by intersecting user grants with optional declarations before creating the bridge. This ensures the sandbox cannot exceed its granted capabilities regardless of the code it executes.
Practical Example: Creating a URL Resolver Plugin
The following example demonstrates a plugin that modifies download URLs before the engine processes them.
Plugin Source
// src/index.ts
import { motrix } from '@motrix/plugin-api'
motrix.hooks.register('resolve', async (ctx) => {
if (ctx.url.startsWith('http')) {
const url = new URL(ctx.url)
url.searchParams.set('utm_source', 'motrix')
return { url: url.toString() }
}
return undefined
})
Manifest Configuration
// motrix-plugin.json
{
"id": "example-resolver",
"name": "Example URL Resolver",
"version": "1.0.0",
"description": "Adds a utm_source query param to HTTP URLs.",
"main": "dist/plugin.js",
"permissions": [],
"optionalPermissions": [],
"contributes": {
"hooks": {
"resolve": {
"role": "resolve",
"description": "Modify URLs before download starts."
}
}
},
"engines": {
"motrix": ">=2.5.0"
}
}
Run pnpm exec motrix-plugin pack to generate example-resolver-1.0.0.moext, then install it through the Motrix Plugins UI.
Key Implementation Files in the Codebase
| Path | Purpose |
|---|---|
src/core/plugin/plugin-registry.ts |
Scans directories, parses manifests, and maintains the plugin index. |
src/core/plugin/host/plugin-host.ts |
Manages worker lifecycles, permission gating, and bridge instantiation. |
src/core/plugin/manifest/parse.ts |
Validates and normalizes motrix-plugin.json against the schema. |
src/core/plugin/manifest/i18n-resolve.ts |
Merges locale files into manifests for UI display. |
src/shared/types/plugin.ts |
Contains TypeScript definitions for manifests, capabilities, and runtime state. |
src/renderer/routes/plugins/* |
React components for the plugin management UI. |
tests/fixtures/plugins/*/dist/plugin.js |
Example compiled bundles used in the test suite. |
Summary
- Motrix plugins are ES2020 modules executed inside a QuickJS sandbox with no direct Node.js or filesystem access.
- The Plugin Registry (
src/core/plugin/plugin-registry.ts) handles discovery and manifest parsing, while the Plugin Host (src/core/plugin/host/plugin-host.ts) manages worker threads and permissions. - All external interactions require explicit capabilities declared in
motrix-plugin.jsonand enforced by the Capability Bridge. - Use
npx create-motrix-pluginto scaffold,npm run devto build, andmotrix-plugin packto generate distributable.moextfiles. - Plugins communicate with Motrix via hooks registered through
@motrix/plugin-api, invoked throughPluginHost.invokeHook().
Frequently Asked Questions
What is the QuickJS sandbox in Motrix?
The QuickJS sandbox is an isolated JavaScript runtime environment that executes plugin code without access to Node.js built-ins or the host filesystem. According to the agalwood/Motrix source code, the Capability Bridge injects a controlled motrix object into this sandbox, allowing plugins to interact with the application only through approved API methods like motrix.hooks.register and motrix.storage.
How does Motrix handle plugin permissions?
Motrix enforces a two-tier permission system: required permissions (always granted) and optional permissions (requiring user approval). The Plugin Host calculates the effective permission set as the union of required permissions and the intersection of optional permissions with user grants before activating the plugin, ensuring the sandbox cannot access unapproved capabilities.
Can Motrix plugins access the network directly?
No. Plugins cannot access the network, filesystem, or Node.js APIs directly. Any network activity must be requested through the network capability in motrix-plugin.json and executed via the Runtime API methods exposed by the Capability Bridge, which the Plugin Host validates against the granted permissions before allowing the operation.
What is the .moext file format?
The .moext file is a signed tarball containing the compiled plugin bundle (dist/plugin.js) and its assets. When you run motrix-plugin pack, the SDK creates this file, which Motrix installs by copying into the plugins/ directory and verifying signatures for built-in packages via verifyBuiltinSignature in the Plugin Host.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →