OmniRoute Plugin System: How the SDK and Marketplace Work Together

The OmniRoute plugin system combines a type-safe SDK, sandboxed execution environment, and RESTful marketplace API to let developers extend the router without modifying core code.

The OmniRoute repository (diegosouzapw/OmniRoute) ships a production-grade plugin framework that isolates third-party extensions in worker threads while exposing a clean JavaScript SDK for hook registration. This article breaks down how the plugin SDK, manager, and marketplace API interact to load, secure, and execute custom logic.

Core Components of the Plugin Architecture

The framework consists of three tightly-coupled layers:

  • Plugin SDK (src/lib/plugins/sdk.ts): Exports the definePlugin() factory and helper utilities (blockRequest, modifyBody, addMetadata) that authors use to declare hooks, permissions, and configuration schemas.
  • Plugin Manager & Registry (src/lib/plugins/manager.ts): Orchestrates discovery, validation, sandboxing, and persistence. It scans the plugin directory (default ~/.omniroute/plugins/ or the path set in OMNIROUTE_PLUGIN_PATH), validates plugin.json manifests against the Zod schema in src/lib/plugins/manifest.ts, and stores state in the SQLite plugins table (src/lib/db/plugins.ts).
  • Marketplace REST API (docs/reference/API_REFERENCE.md): Exposes endpoints under /api/plugins/* for listing, installing, upgrading, activating, and configuring plugins via HTTP.

Plugin Discovery and Loading Process

When the server initializes, the pluginManager.loadAll() method triggers a strict lifecycle:

1. Discovery and Validation

The scanner (src/lib/plugins/scanner.ts) walks the plugin directory and reads each plugin.json. The manifest schema in src/lib/plugins/manifest.ts validates required fields (name, version, main entry point) and optional hooks declarations.

2. Sandboxed Execution

Each plugin’s entry point (default index.js) runs inside a dedicated worker thread (src/lib/plugins/pluginWorker.ts). This isolates memory and I/O from the host process. If the environment variable OMNIROUTE_PLUGINS_ALLOW_EXEC is set to 1, the worker may spawn a child process for privileged operations.

3. Hook Registration

The object returned by definePlugin() declares hooks such as onRequest, onResponse, onError, onInstall, onActivate, onDeactivate, and onUninstall. The manager registers these in a per-plugin registry and wires them into the request pipeline via open-sse/handlers/chatCore/pluginOnRequest.ts and pluginOnResponse.ts.

Hook Execution and Request Pipeline

During runtime, the OmniRoute plugin system executes hooks in a specific order:

  • onRequest: Fires when a request enters the router. Plugins can mutate the request, block it entirely, or add metadata.
  • onResponse: Fires after the upstream response is received. Plugins can transform the body or emit analytics.
  • onError: Invoked when the router encounters an error, allowing plugins to handle or log failures.
  • Lifecycle hooks (onInstall, onActivate, onDeactivate, onUninstall): Run during state transitions triggered by the marketplace API.

Each hook is wrapped with a per-plugin rate limiter to prevent a misbehaving extension from degrading request latency.

Marketplace API and Lifecycle Management

The marketplace is not a separate service but a set of REST endpoints that treat any URL or local path as a distribution source.

Installation Flow

Clients POST to /api/plugins/install with either a local filesystem path or a remote tarball URL. The manager downloads the archive (if remote), verifies the SHA-256 hash to detect tampering, extracts it, and runs the same manifest validation used during local discovery.

Version Management

The upgrade method compares semantic versions using an internal compareSemver utility. It refuses downgrades, ensuring only newer versions replace existing installations.

Activation and Configuration

After installation, plugins can be auto-activated if enabledByDefault is true in the manifest, or manually via POST /api/plugins/:name/activate. Activation spins up the sandbox and registers hooks. Plugins exposing a configSchema in their manifest support dynamic configuration via GET and PUT /api/plugins/:name/config.

Security and Isolation Controls

The framework implements defense-in-depth for third-party code:

  • SHA-256 Integrity: Every plugin installed from a URL is hashed on download; subsequent loads verify the hash against the stored value.
  • Permission Model: Plugins declare required capabilities (e.g., file-system access, network calls) in manifest.requires.permissions. The manager validates these against the host policy before activation.
  • Environment Overrides: Set OMNIROUTE_PLUGINS_ALLOW_EXEC=1 only when child-process privileges are required; otherwise, worker threads enforce strict isolation.

Building a Plugin with the SDK

The following example creates a request-blocking plugin using the OmniRoute plugin SDK.

TypeScript source (src/plugins/example/plugin.ts):

import { definePlugin, blockRequest } from "omniroute/plugins/sdk";

export default definePlugin({
  name: "example",
  version: "1.0.0",
  description: "Blocks requests containing the word “spam”.",
  hooks: {
    onRequest: true,
  },
  async onRequest({ request }) {
    if (request.body?.includes("spam")) {
      // Use an SDK helper to abort the request with a 400 error.
      return blockRequest(400, "Spam content is not allowed");
    }
  },
});

Corresponding manifest (plugin.json):

{
  "name": "example",
  "version": "1.0.0",
  "main": "plugin.ts",
  "hooks": { "onRequest": true },
  "requires": { "permissions": [] }
}

Deploy via CLI:

omniroute plugins install ~/.omniroute/plugins/example
omniroute plugins activate example

Summary

Frequently Asked Questions

How do I install a plugin from a remote URL in OmniRoute?

Post the URL to POST /api/plugins/install. The manager downloads the tarball, verifies its SHA-256 hash, validates the plugin.json manifest, and extracts it to the configured plugin directory.

What hooks can an OmniRoute plugin implement?

Plugins can implement onRequest, onResponse, onError, onInstall, onActivate, onDeactivate, and onUninstall. These are declared in the hooks field of the object returned by definePlugin().

How does OmniRoute isolate plugins from the core system?

By default, plugins run inside worker threads (src/lib/plugins/pluginWorker.ts) that isolate memory and I/O. Only when OMNIROUTE_PLUGINS_ALLOW_EXEC=1 is set can a plugin spawn a child process, and even then, the manager enforces permission checks and rate limits.

Where does OmniRoute store plugin state and configuration?

Plugin metadata and state persist in the SQLite plugins table defined in src/lib/db/plugins.ts. Runtime configuration is exposed via the REST API (/api/plugins/:name/config) and validated against the JSON schema declared in the plugin manifest.

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 →