What Database Does Motrix Use and How Is It Integrated?

Motrix uses an embedded SQLite database accessed through the better-sqlite3 Node.js library, wrapped in a TypeScript MotrixDatabase class that provides schema migrations, transaction management, and type-safe CRUD operations.

The open-source download manager agalwood/Motrix persists all user tasks, download instances, and notification history locally using SQLite rather than a client-server database. This architecture eliminates external dependencies while ensuring fast, reliable storage that synchronizes seamlessly with the Electron application's lifecycle.

Database Engine and Dependencies

At the core of Motrix's persistence layer sits SQLite, exposed to the Node.js runtime via the native addon better-sqlite3 (version ^13.0.3). This dependency is declared in the project's package.json and provides synchronous, high-performance access to the embedded database engine.

The library is imported directly into the core database module:

import Database from 'better-sqlite3'

You can find this import statement in src/core/session/motrix-database.ts at lines 25-27. Unlike traditional databases that require a separate server process, SQLite operates directly on a local file, making it ideal for a desktop application like Motrix that needs to maintain state across user sessions without external configuration.

The MotrixDatabase Wrapper Class

Rather than interacting with better-sqlite3 primitives throughout the application, Motrix centralizes all database logic inside the MotrixDatabase class defined in src/core/session/motrix-database.ts. This wrapper encapsulates a private BetterSqlite3.Database instance and exposes high-level methods for executing queries, managing transactions, and validating data schemas using Zod.

Key responsibilities of the MotrixDatabase class include:

  • Schema migrations – Automatically creating and updating tables (tasks, task_instances, task_files, notifications) when the application starts.
  • Type safety – Enforcing runtime validation for row types such as TaskRow, TaskInstanceRow, and Notification before insertion or retrieval.
  • Transaction management – Grouping multiple write operations into atomic transactions to prevent data corruption during concurrent updates.

The constructor initializes the underlying SQLite connection with error handling for busy states and foreign key constraints:

// Simplified conceptual view based on lines 91-93 of motrix-database.ts
this.db = new Database(dbPath, {
  timeout: 5000,
  verbose: process.env.NODE_ENV === 'development' ? console.log : undefined
});

Session Layer Integration

The SessionManager (located in src/core/session/session-manager.ts) orchestrates the database lifecycle. During application startup, it instantiates a single MotrixDatabase instance and loads the persisted task state. This singleton pattern ensures that all modules—task handlers, notification engines, and IPC handlers—share a consistent data source.

The integration follows this sequence:

  1. Initialization – SessionManager creates the database wrapper, passing the user data directory path (e.g., app.getPath('userData') + '/motrix.db').
  2. Restore – On launch, the manager calls motrixDb.getAllTasks() to reload incomplete downloads into memory.
  3. Persistence ticks – At regular intervals or upon state changes, the manager invokes methods like saveTasksBatch() to persist deltas to SQLite.
  4. Cleanup – Old notification entries and completed task history are pruned via the database wrapper's maintenance methods before the application exits.

Practical Usage Examples

The following patterns demonstrate how Motrix interacts with its SQLite backend through the MotrixDatabase API.

Initializing the Database

Applications or test suites can instantiate the database with either a file path or in-memory storage:

import { MotrixDatabase } from '@core/session/motrix-database';
import path from 'path';
import { app } from 'electron';

const dbPath = path.join(app.getPath('userData'), 'motrix.db');
const motrixDb = new MotrixDatabase(dbPath);
await motrixDb.init(); // Executes migrations and validates schema

Persisting Task Batches

When a user creates or updates downloads, the application batches these changes into a single transaction to ensure consistency across related tables:

// tasks: Array<TaskWithInstancesAndFiles>
await motrixDb.saveTasksBatch(tasks);

Under the hood, saveTasksBatch constructs a prepared statement transaction that upserts rows into the tasks, task_instances, and task_files tables. It employs a content-signature cache to skip rows that haven't changed, optimizing write performance during high-frequency update cycles.

Querying by Motrix ID

Individual task lookups use indexed queries on the tasks table, automatically joining related instances:

const task = await motrixDb.getTaskByMotrixId('abc123');
if (task) {
  console.log(`Found task: ${task.task.name}`);
}

Inserting Notifications

The notification system leverages database transactions to maintain data integrity between the notifications and notification_occurrences tables:

await motrixDb.insertNotificationWithLedger({
  sourceKey: 'engine-start',
  taskId: null,
  kind: 'info',
  severity: 'info',
  titleKey: 'engine.started',
  titleParams: null,
  bodyKey: null,
  bodyParams: null,
  createdAt: Date.now(),
});

If the insertion violates temporal constraints (detected via NotificationInsertStaleError), the transaction rolls back automatically, preventing duplicate or out-of-order notification records.

Summary

  • Motrix relies on SQLite integrated via the better-sqlite3 npm package (^13.0.3) declared in package.json.
  • All database operations are abstracted through the MotrixDatabase class in src/core/session/motrix-database.ts, which handles connections, migrations, and Zod-based schema validation.
  • The SessionManager instantiates this wrapper at startup and coordinates state persistence, ensuring the UI layer remains database-agnostic.
  • Transaction safety is enforced for batch operations involving tasks, files, and notifications, preventing partial writes during application crashes or power failures.

Frequently Asked Questions

What database does Motrix use?

Motrix uses SQLite as its embedded database engine, accessed through the Node.js library better-sqlite3. This allows the application to store all download tasks, metadata, and notification history in a single local file without requiring a separate database server installation.

Why does Motrix use SQLite instead of a client-server database?

SQLite is serverless and self-contained, making it ideal for a desktop Electron application like Motrix. It eliminates network latency, simplifies deployment (no separate installation required), and provides ACID compliance for local data persistence while maintaining a small footprint on the user's system.

How does Motrix handle database schema migrations?

The MotrixDatabase class in src/core/session/motrix-database.ts executes schema migrations automatically during the init() call. It checks the current schema version stored in SQLite's user_version pragma and runs incremental DDL statements to create new tables or alter existing ones, ensuring compatibility across application updates.

Where is the Motrix database file stored on disk?

By default, the database file is located in the application's user data directory, resolved via Electron's app.getPath('userData') API. The specific filename is motrix.db, though this path can be customized through settings defined in src/shared/types/settings.ts under the sqlite3DbPath configuration key.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →