Plugin System Overview
This document explains the design philosophy, architecture, and key concepts of the OpenGPEX plugin system. Whether you want to build a custom tool or understand how the editor is structured, start here.
Design Philosophy
OpenGPEX strictly follows Inversion of Control (IoC) and the Open-Closed Principle to achieve complete decoupling between the core engine framework (Workspace) and business plugins.
┌─────────────────────────────────────────────────────────┐
│ Core Engine (Workspace) │
│ │
│ Registry ──── auto-discovers ────► Plugin A │
│ ────► Plugin B │
│ ────► Plugin C │
│ │
│ Workspace ── renders via Slots ── Registry │
└─────────────────────────────────────────────────────────┘
The core never imports plugins directly. Instead, plugins register themselves into a metadata-driven registry, and the workspace dynamically renders them into physical slots.
Dual-Track Loading Architecture
OpenGPEX uses a dual-track plugin loading system:
| Track | Method | Use Case |
|---|---|---|
| Track 1: Static | Source compiled into the Next.js bundle | Official built-in plugins, local development |
| Track 2: Dynamic | ESM ZIP loaded at runtime via import() |
Production hot-plug install, user uploads |
┌─────────────────────────────┬────────────────────────────────┐
│ Track 1: Static Compile │ Track 2: Dynamic Runtime │
│ │ │
│ plugins/base/ → bundle │ data/plugins/user/ → ESM load │
│ plugins/community/ → bundle │ /api/plugins/serve (proxy) │
│ plugins/user/ → bundle │ /api/plugins/upload (ingest) │
│ │ │
│ registry.ts (generated) │ /api/plugins/list (discovery) │
└─────────────────────────────┴────────────────────────────────┘
Trust Model (sourceType)
Every plugin carries a trust level:
| sourceType | Meaning | Install Method | Removable | Disablable |
|---|---|---|---|---|
base |
Official core features | Compiled into bundle | ❌ | ❌ |
community |
Verified third-party, pre-installed | Compiled into bundle | ❌ | ✅ |
user |
User-installed | ZIP upload / Hub download | ✅ | ✅ |
📌 Users can never elevate a plugin to
communitystatus at runtime. Only plugins shipped with the release qualify.
UID Namespace System
Every plugin, command, and signal receives a globally unique identifier (UID):
| Entity | Formula | Example |
|---|---|---|
| Plugin | author + "." + manifest.id |
opengpex.drawers.adjustment |
| Command | plugin.uid + "." + cmd.id |
opengpex.drawers.adjustment.cmd.reset |
| Signal | plugin.uid + "." + sig.id |
opengpex.drawers.adjustment.signal.active_tab |
This ensures zero naming collisions even when multiple plugins declare commands with the same local name.
Plugin Metadata Contract
Every plugin exports a plugin object implementing the EditorPlugin interface:
export const plugin: EditorPlugin = {
// 1. Identity
manifest: {
id: 'drawers.my_tool',
displayName: 'My Tool',
version: '1.0.0',
description: 'A custom drawing tool',
category: 'drawers',
author: 'opengpex',
},
// 2. Layout
slot: 'SIDE_BAR',
show: 'frame-required',
icon: <Palette size={20} />,
order: 400,
// 3. Implementation
component: MyToolComponent,
initialConfig: { mode: 'normal' },
// 4. Capabilities
commands: [/* ... */],
signals: [/* ... */],
interactions: [/* ... */],
// 5. Lifecycle
onInit: (ctx) => { /* setup */ },
onDestroy: (ctx) => { /* cleanup */ },
};
Slot System
Plugins render into physical slots — predefined UI regions in the workspace:
| Slot | Location | Plugin Suffix Convention |
|---|---|---|
SIDE_BAR |
Left sidebar panels | XxxDrawer, XxxPanel |
OPTION_BAR |
Top options strip | XxxOptions |
VIEWPORT_OVERLAY |
Canvas overlay (handles, guides) | XxxOverlay |
STAGE_OVERLAY |
Stage-level overlay | XxxOverlay |
HIDDEN |
No UI, background service | TimeTraveler, etc. |
DOCK |
Bottom dock area | TabDock |
Plugin Lifecycle
Install/Enable → onInit(ctx) → Active (renders in slot) → Disable → onDestroy(ctx)
- Registration — Plugin metadata is scanned and added to the registry
- Initialization —
onInitfires with full editor context - Active — Component renders in its assigned slot, commands/signals are live
- Destruction —
onDestroyfires for cleanup when disabled or uninstalled
The 5-File Pattern
Every plugin follows a standard file structure:
plugins/base/[category]/[PluginName]/
├── index.tsx # Plugin entry — exports EditorPlugin object
├── protocols.ts # Constants (PLUGIN_ID, command IDs, config interfaces)
├── commands.ts # Command executors (business logic)
├── hooks.ts # Custom hooks (bridge UI ↔ commands/signals)
└── components.tsx # React UI components (pure presentation)
Next Steps
- Your First Plugin — Build a working plugin from scratch
- API Reference — Full hook and command documentation
- Slots & UI — Detailed slot types and Z-Index protocol
Last updated: 2026-06-14