Files
ObsiGate/docs/PLUGINS.md
T
bruno 30f2df3004
CI / lint (push) Failing after 30s
CI / test (push) Skipped
CI / build (push) Skipped
CI / e2e (push) Skipped
CI / security (push) Successful in 35s
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s
feat: #61 Plugin system — backend API, sandboxed Web Worker, hooks wired to real app flow, user guide, 47+21 tests
2026-09-10 09:01:38 -04:00

8.0 KiB

Plugin System — ObsiGate #61

Overview

ObsiGate's plugin system enables extending the application with custom renderers, search filters, and editor actions — all sandboxed for security.

Plugins run in a Web Worker sandbox and communicate with the main app via structured postMessage. No plugin can access the DOM, localStorage, or make network requests without explicit permission.

Quick Start

Installing a Plugin

  1. Go to Settings > Plugins in the sidebar
  2. Click Install Plugin
  3. Upload a .zip file or a directory containing:
    • plugin.json — the manifest
    • Your main JS file (entry point specified in main)

Creating a Plugin

Use the View Template button in Settings > Plugins to get started.

Manifest (plugin.json)

{
  "name": "my-plugin",
  "version": "1.0.0",
  "description": "A brief description of what this plugin does",
  "author": "your-name",
  "main": "index.js",
  "hooks": {
    "onFileRender": "renderFile",
    "onSearchFilter": "filterResults",
    "onEditorAction": "handleAction"
  },
  "permissions": ["read_files"],
  "min_obsigate_version": "2.1.0",
  "license": "MIT",
  "homepage": "https://example.com",
  "repository": "https://github.com/user/plugin"
}

Required Fields

Field Type Description
name string Lowercase alphanumeric + hyphens (e.g. my-plugin)
version string Semantic version (e.g. 1.0.0, 1.0.0-beta.1)
description string Brief description
author string Author name
main string Entry point file (relative to plugin root)

Optional Fields

Field Type Default Description
hooks object {} Map of hook name → handler function name
permissions array [] Required permissions
min_obsigate_version string "2.1.0" Minimum ObsiGate version
license string "MIT" License identifier
homepage string null Plugin homepage URL
repository string null Source repository URL

Available Hooks

Hook When it fires Handler signature
onFileRender Before a file is rendered (ctx) → transformed ctx
onSearchFilter During search result filtering (results) → filtered results
onEditorAction When editor action is triggered (action, state) → result
onSidebarItem Sidebar item is rendered (item) → enhanced item
onFileCreate After a file is created (file) → void
onFileDelete After a file is deleted (file) → void
onVaultMount When a vault is mounted (vault) → void

Permissions

Plugins must declare the permissions they need. The sandbox enforces these restrictions at runtime.

Permission Description
read_files Read file contents from the vault
write_files Write/create files in the vault
read_vault_metadata Access vault configuration and metadata
network_request Make HTTP requests to external services
ui_notify Show toast notifications in the UI
access_clipboard Read/write to the system clipboard

Security Model

Sandbox Architecture

┌─────────────────────────────────┐
│  Main App (browser context)     │
│                                 │
│  PluginManager                  │
│    ├── install/uninstall        │
│    ├── enable/disable           │
│    └── hook dispatch            │
│         │ postMessage (C3)      │
│         ▼                       │
│  ┌─────────────────────────┐    │
│  │  Web Worker Sandbox     │    │
│  │  (no DOM, no localStorage│   │
│  │   no network by default) │   │
│  │                          │   │
│  │  Plugin code executes    │   │
│  │  here with structured    │   │
│  │  message passing only    │   │
│  └─────────────────────────┘    │
└─────────────────────────────────┘

Security Guarantees

  • C1: Manifest validation (name format, semver, allowed hooks/permissions)
  • C2: Vault isolation — plugins are scoped to a single vault
  • C3: Web Worker sandbox — no DOM, no localStorage, no importScripts
  • C4: ZIP validation — path traversal, file count, size limits
  • C5: Directory validation — manifest + entry point presence
  • C6: Permission enforcement at sandbox boundary

Limits

  • Max 50 plugins per vault
  • Max 500 KB per plugin file
  • Max 100 files per plugin ZIP
  • No dynamic import() or eval()

Plugin Storage

Plugins are installed under <vault>/.obsigate-plugins/<plugin-name>/:

.obsigate-plugins/
  my-plugin/
    plugin.json      # manifest
    index.js         # entry point
    .disabled        # marker file (created when disabled)

API Reference

Backend Endpoints

Method Endpoint Auth Description
GET /api/plugins require_auth List installed plugins
POST /api/plugins/install require_admin Install from ZIP
DELETE /api/plugins/{name} require_admin Uninstall plugin
POST /api/plugins/{name}/enable require_admin Enable plugin
POST /api/plugins/{name}/disable require_admin Disable plugin
GET /api/plugins/{name}/code/{file} require_auth Get plugin code
GET /api/plugins/{name}/hooks require_auth Get plugin hooks
GET /api/plugins/template require_auth Get plugin template

Frontend Module (plugins.js)

import { PluginManager } from './plugins.js';

// Install
await PluginManager.installPlugin(manifest, code);

// Enable/Disable
await PluginManager.enablePlugin('my-plugin');
await PluginManager.disablePlugin('my-plugin');

// Uninstall
await PluginManager.uninstallPlugin('my-plugin');

// Get code for sandbox
const code = await PluginManager.getPluginCode('my-plugin', 'index.js');

// List
const plugins = await PluginManager.getCachedPlugins();

Creating Your First Plugin

  1. Create a directory with plugin.json:
{
  "name": "hello-world",
  "version": "1.0.0",
  "description": "My first ObsiGate plugin",
  "author": "you",
  "main": "index.js",
  "hooks": {
    "onFileRender": "render"
  },
  "permissions": ["read_files", "ui_notify"]
}
  1. Create index.js:
export function render(ctx) {
  // Add a custom header to rendered markdown
  if (ctx.content && ctx.path.endsWith('.md')) {
    ctx.content = `> 📝 Plugin: hello-world\n\n${ctx.content}`;
  }
  return ctx;
}
  1. Zip the directory and install via Settings > Plugins > Install Plugin.