Best Practices for Developing with the Motrix Codebase: A Complete Guide
To develop efficiently with the Motrix codebase, always route window creation through the centralized WindowManager class, isolate business logic within the src/core/ directory, enforce renderer URL policies for IPC security, and extend functionality via the sandboxed QuickJS plugin system using the MDXP protocol bridge.
Motrix is a full-featured download manager built on Electron that cleanly separates concerns across main, renderer, and server processes. Whether you are contributing to the agalwood/Motrix repository or building plugins for the ecosystem, understanding these architectural boundaries is essential for writing secure, maintainable code.
Understanding the Multi-Process Architecture
Motrix follows Electron's multi-process model with strict separation between native window management, UI rendering, and download engine logic.
Main Process and Window Management
The main process handles all native OS interactions and window lifecycle events. According to the source code, you must never instantiate BrowserWindow directly; instead, use the centralized WindowManager class.
- Obtain windows via
WindowManager.open()— This ensures consistent creation, restoration, and bounds persistence across sessions. - Apply platform-specific options — Use
buildPlatformOptions()fromsrc/main/window/platform-options.tsto isolate OS-specific UI decisions from business logic. - Enforce security policies — Pass a
rendererUrlPolicyobtained fromgetRendererUrlPolicy()in [src/main/window/renderer-url-policy.ts](https://github.com/agalwood/Motrix/blob/main/src/main/window/renderer-url-policy.ts) to every new window to prevent injection attacks.
Renderer Process and Localization
The renderer process contains the React-based UI and must remain sandboxed from Node.js APIs.
- Type-safe internationalization — Import the
i18nmodule from [src/renderer/lib/i18n.ts](https://github.com/agalwood/Motrix/blob/main/src/renderer/lib/i18n.ts), which provides Zod-derived schemas guaranteeing UI strings are validated at compile-time. - Centralized formatting — Use
src/renderer/lib/format.tsfor number, size, and time formatting to avoid duplicating locale logic across components.
Server and Core Logic
All download engine functionality lives outside the Electron main process, enabling Docker-ready headless deployment.
- HTTP and RPC layer — Express-style routing lives in [
src/server/http/app.ts](https://github.com/agalwood/Motrix/blob/main/src/server/http/app.ts). This is the sole entry point for external communications, including web UI and MDXP clients. - Business logic isolation — Keep all download-related business logic in the
src/core/tree. For example, tracker health checks and automatic updates are coordinated by [src/core/tracker/tracker-manager.ts](https://github.com/agalwood/Motrix/blob/main/src/core/tracker/tracker-manager.ts). - Task persistence — The
task-persistence.tsmodule provides a SQLite-backed store that survives application restarts.
Secure Development Guidelines
Security in Motrix relies on strict validation at process boundaries.
- Validate IPC channels — Any IPC communication crossing process boundaries must be validated against the
rendererUrlPolicy. This enforcement is implemented in [src/shared/protocol/bridge.ts](https://github.com/agalwood/Motrix/blob/main/src/shared/protocol/bridge.ts). - Restrict renderer capabilities — Never expose Node.js APIs directly to the renderer. All privileged operations must go through the preload script and the MDXP bridge.
- Use type-safe schemas — Motrix leverages Zod for runtime validation (see
src/renderer/lib/json-schema-to-zod.ts). Extend existing schemas rather than creating ad-hoc type definitions.
Extending Functionality via the Plugin System
Motrix supports sandboxed plugins running in a QuickJS environment without Node.js access.
MDXP Protocol and Aria2 Wrapper
- Bridge layer — The [
src/shared/protocol/bridge.ts](https://github.com/agalwood/Motrix/blob/main/src/shared/protocol/bridge.ts) converts MDXP JSON-RPC messages into internal commands. This is the only location where external JSON-RPC is parsed, making it the natural spot for validation. - Download engine abstraction — Plugins must interact with the aria2 wrapper in [
src/shared/platform/aria2.ts](https://github.com/agalwood/Motrix/blob/main/src/shared/platform/aria2.ts) rather than invoking aria2 directly. - Type definitions — When adding capabilities, extend the TypeScript definitions in [
src/shared/types/plugin.ts](https://github.com/agalwood/Motrix/blob/main/src/shared/types/plugin.ts) and updatesrc/shared/protocol/commands.tsso the bridge can forward new commands.
Plugin Sandboxing
Plugins run in isolated QuickJS contexts. Declare required capabilities in motrix-plugin.json; Motrix will prompt users before granting them. Use the official motrix-plugin scaffolder (pnpm create motrix-plugin) to ensure manifests conform to the SDK specifications.
Development Workflow and Testing
Efficient development requires understanding the build pipeline and testing hierarchy.
Environment Setup
# Install prerequisites (Node ≥22, pnpm)
curl -fsSL https://get.pnpm.io/install.sh | sh
pnpm install
Running and Debugging
# Development mode with hot-reloading for main and renderer processes
pnpm dev
# Unit tests with Vitest
pnpm test
# Integration and E2E tests with Playwright
pnpm test:e2e
Code Quality
# Linting and formatting
pnpm lint
# Type checking without emission
pnpm typecheck
Best practice: Write unit tests against core modules (e.g., src/core/tracker/tracker-manager.test.ts) before exposing new HTTP endpoints. Keep the CI pipeline fast by running only affected tests locally via pnpm test:watch.
Practical Implementation Examples
Adding a New Window Type
// src/main/window/custom-window.ts
import { WindowManager } from './window-manager';
import { WINDOW_CONFIGS } from './window-configs';
export function createStatsWindow(manager: WindowManager) {
// Register configuration in src/main/window/window-configs.ts first
manager.open('stats', { show: true });
}
Always register new window configurations in WINDOW_CONFIGS before calling manager.open().
Extending the MDXP Bridge
// src/shared/protocol/commands.ts
export const Commands = {
...ExistingCommands,
GetServerHealth: 'getServerHealth',
};
// src/server/http/app.ts
router.post('/rpc', async (req, res) => {
const { method, params } = req.body;
if (method === Commands.GetServerHealth) {
const health = await getServerHealth(); // core function from src/core/
return res.json({ result: health });
}
// Fallback to existing bridge handler
});
Implementing a Simple Plugin
// my-plugin/src/index.ts
import { definePlugin } from '@motrix/plugin-api';
export default definePlugin({
id: 'hello-world',
version: '0.1.0',
manifest: {
name: 'Hello World',
capabilities: ['readNetwork'],
activationEvents: ['onAddTask'],
},
async onAddTask(context) {
console.log('New task added:', context.task);
// Modify task before processing
context.task.url = context.task.url.replace('http://', 'https://');
},
});
Summary
- Centralize window management — Always use
WindowManager.open()from [src/main/window/window-manager.ts](https://github.com/agalwood/Motrix/blob/main/src/main/window/window-manager.ts) rather than instantiatingBrowserWindowdirectly. - Isolate business logic — Keep download engine code in
src/core/and HTTP routing in [src/server/http/app.ts](https://github.com/agalwood/Motrix/blob/main/src/server/http/app.ts). - Enforce security policies — Validate all IPC against the
rendererUrlPolicydefined in [src/main/window/renderer-url-policy.ts](https://github.com/agalwood/Motrix/blob/main/src/main/window/renderer-url-policy.ts). - Use type-safe i18n — Import formatting utilities from [
src/renderer/lib/i18n.ts](https://github.com/agalwood/Motrix/blob/main/src/renderer/lib/i18n.ts) to handle localization. - Develop plugins safely — Extend functionality via the MDXP bridge in [
src/shared/protocol/bridge.ts](https://github.com/agalwood/Motrix/blob/main/src/shared/protocol/bridge.ts) and the aria2 wrapper in [src/shared/platform/aria2.ts](https://github.com/agalwood/Motrix/blob/main/src/shared/platform/aria2.ts), respecting QuickJS sandbox limitations.
Frequently Asked Questions
How do I add a new window type to Motrix?
Register the window configuration in src/main/window/window-configs.ts, then obtain an instance via WindowManager.open('yourWindowId') in [src/main/window/window-manager.ts](https://github.com/agalwood/Motrix/blob/main/src/main/window/window-manager.ts). Never instantiate BrowserWindow directly to ensure proper lifecycle management and security policy enforcement.
What is the correct way to add a new RPC command?
Add the command string to src/shared/protocol/commands.ts, then implement the handler in [src/server/http/app.ts](https://github.com/agalwood/Motrix/blob/main/src/server/http/app.ts). Delegate to functions in src/core/ for business logic rather than implementing logic directly in the HTTP layer.
Can I access Node.js APIs from a Motrix plugin?
No. Plugins run in a sandboxed QuickJS environment with no Node.js APIs available. You must declare required capabilities in motrix-plugin.json and interact with the system through the MDXP bridge defined in [src/shared/protocol/bridge.ts](https://github.com/agalwood/Motrix/blob/main/src/shared/protocol/bridge.ts) and the type definitions in [src/shared/types/plugin.ts](https://github.com/agalwood/Motrix/blob/main/src/shared/types/plugin.ts).
How does Motrix handle tracker management?
Tracker health checks and updates are coordinated by [src/core/tracker/tracker-manager.ts](https://github.com/agalwood/Motrix/blob/main/src/core/tracker/tracker-manager.ts). This module runs in the server process, separate from the Electron main process, and persists state via the SQLite-backed task persistence layer.
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 →