Launch OpenGPEX

Plugin System

How OpenGPEX achieves full extensibility through a metadata-driven plugin registry — where every UI element, tool, and feature is a plugin, including all built-in functionality.


Design Philosophy

OpenGPEX follows strict Inversion of Control (IoC): the core framework provides layout slots and lifecycle management, while plugins provide all business functionality. The editor ships zero hardcoded UI — every panel, tool, overlay, and backstage service is a plugin.

┌──────────────────────────────────────┐
│       Core Workspace (Framework)      │
│                                       │
│  ┌─────────────┐  ┌───────────────┐  │
│  │  Slot Mgr   │  │   Registry    │  │
│  │  (renders)  │◄─┤  (discovers)  │  │
│  └─────────────┘  └───────┬───────┘  │
│                           │           │
└───────────────────────────┼───────────┘
                            │ auto-scan
        ┌───────────────────┼───────────────────┐
        ▼                   ▼                   ▼
  [LayerPanel]       [CropTool]          [FileLoader]
  [UndoPlugin]       [TextTool]          [AdjustDrawer]
        ...                                   ...

Dual-Track Registry

Plugins are registered in two independent registries, loaded in sequence:

Registry Location Purpose
Core Registry core/plugin/registry.ts Built-in official plugins, build-tool maintained
User Registry plugins/registry-user.ts Local/community plugins, auto-scanned

The PluginService unifies both registries for lifecycle management (init → activate → deactivate → dispose).


Plugin Metadata Contract

Every plugin exports an EditorPlugin object containing:

  • manifest — Identity (author, id, version, description). Runtime UID = author.id
  • slot — Where the plugin renders (sidebar, overlay, options bar, hidden, etc.)
  • component — React component for the plugin's UI (if any)
  • commands — Dispatchable command definitions (keyboard shortcuts, menu actions)
  • interactions — Gesture handlers with priority-based routing
  • onAction — Interceptor for global state actions (reactive side-effects)
  • config — Default configuration with scoped persistence

Slot System

Plugins are rendered into physical layout slots:

Slot Location Examples
SIDE_BAR Left/right panel area Layer Panel, Adjustment Drawer
OPTION_BAR Top toolbar Crop Options, Viewport Options
VIEWPORT_OVERLAY Over the canvas Crop handles, Transform gizmos
STAGE_OVERLAY Behind layers Pixel grid, Smart guides
HIDDEN No UI (background service) TimeTraveler, AutoSave
DOCK Bottom dock Tab navigation

Command System

Plugins define commands identified by scoped UIDs:

  • Within a plugin: Short IDs auto-resolve to own namespace (e.g., cmd.resetopengpex.drawers.my_tool.cmd.reset)
  • Cross-plugin: Full UID required (e.g., opengpex.panels.layer_panel.cmd.select_layer)

Commands support interceptors, async execution, and keyboard shortcut binding.


Interaction Handlers

Plugins register gesture handlers with a priority-based routing system:

Priority Range Use Case
100–200 Overlay handles (crop box, resize corners)
50–99 Active tool interactions (brush, text)
10–49 Layer selection & move
0–9 Viewport pan/zoom (fallback)

The dispatcher tests handlers in priority order; first match wins and owns the entire gesture lifecycle (start → move → end).


Safety & Isolation

  • Error boundaries: Plugin rendering errors are caught per-slot, preventing cascade crashes
  • Scoped state: Each plugin gets isolated config storage via scoped.updateConfig()
  • Action broadcasting: Plugin onAction hooks run asynchronously, cannot block the dispatch pipeline
  • Busy state: Long-running plugin operations expose a standardized busy indicator without blocking the editor

Last updated: 2026-07-30