117
LVL 04 — SENIOR-IN-TRAININGSESSION 117DAY 117

PLUGIN SYSTEM

🎫 PIXELCRAFT-103
Feature | 🔴 Expert | Priority: 🟠 High

Users want custom filters we haven't built. Third-party developers want to extend PixelCraft. Building every possible feature in-house doesn't scale. Design a plugin system: third-party filters loaded dynamically, sandboxed for security, with a clean API for toolbar buttons, event hooks, and canvas access.
CONCEPTS.UNLOCKED
🔌
Plugin Architecture
Core app + extensible surface area. The core handles editing, the plugin API exposes hooks. Third parties build on top without modifying core code. VS Code, Figma, Photoshop — all plugin-based.
📦
Dynamic Loading
Load plugins at runtime, not compile time. import() dynamically fetches plugin code from a URL or registry. Plugins install without redeploying the app. Enable/disable without rebuilding.
📐
Plugin API Design
Define what plugins CAN do and what they CAN'T. Expose: register filters, add toolbar buttons, hook into events. Hide: internal state, authentication, database access. The API surface is your contract.
🔒
Sandboxing
Plugins run in isolation — can't access what you don't expose. Use iframes, Web Workers, or a restricted API object. A malicious plugin shouldn't be able to read user data or modify other plugins.
🪝
Event Hooks
Plugins react to app events without coupling. beforeFilterApply, afterImageLoad, onExport — plugins subscribe to lifecycle events. The Observer pattern from session 099, formalized as an extension API.
📋
Plugin Registry
A catalog of available plugins. Name, version, author, description, permissions required. Like npm for PixelCraft extensions. Users browse, install, and manage plugins from a settings panel.
HANDS-ON.TASKS
01
Define the Plugin Interface
// types/plugin.ts interface PixelCraftPlugin { id: string; name: string; version: string; author: string; description: string; permissions: PluginPermission[]; // Lifecycle activate(api: PluginAPI): void; deactivate(): void; } type PluginPermission = | 'canvas:read' | 'canvas:write' | 'toolbar:add' | 'events:subscribe' | 'storage:read' | 'storage:write'; interface PluginAPI { // Canvas access getImageData(): ImageData; setImageData(data: ImageData): void; getCanvasSize(): { width: number; height: number }; // UI extension addToolbarButton(btn: { id: string; label: string; icon: string; onClick: () => void; }): void; addFilterPanel(panel: { id: string; label: string; render: ( container: HTMLElement ) => void; }): void; // Events on(event: PluginEvent, handler: Function): void; off(event: PluginEvent, handler: Function): void; // Storage (per-plugin, sandboxed) storage: { get(key: string): Promise<any>; set(key: string, value: any): Promise<void>; }; } type PluginEvent = | 'image:loaded' | 'filter:before-apply' | 'filter:after-apply' | 'export:before' | 'tool:changed';
02
Plugin Manager
// lib/pluginManager.ts class PluginManager { private plugins = new Map<string, PluginInstance>(); private eventBus = new EventEmitter(); async install( pluginUrl: string ): Promise<void> { // Dynamic import from URL const module = await import( /* webpackIgnore: true */ pluginUrl); const plugin: PixelCraftPlugin = module.default; // Validate required fields if (!plugin.id || !plugin.name || !plugin.activate) throw new Error( 'Invalid plugin structure'); // Create sandboxed API const api = this.createSandboxedAPI( plugin.id, plugin.permissions); // Activate plugin.activate(api); this.plugins.set(plugin.id, { plugin, api, enabled: true, }); console.log( `✅ Plugin installed: ` + `${plugin.name} v${ plugin.version}`); } async uninstall( pluginId: string ): void { const instance = this.plugins.get(pluginId); if (!instance) return; instance.plugin.deactivate(); this.plugins.delete(pluginId); } emit(event: PluginEvent, data?: any) { this.eventBus.emit(event, data); } }
03
Sandboxed API
// lib/pluginManager.ts (continued) private createSandboxedAPI( pluginId: string, permissions: PluginPermission[] ): PluginAPI { const guard = ( perm: PluginPermission, action: string ) => { if (!permissions.includes(perm)) throw new Error( `Plugin "${pluginId}" ` + `lacks permission: ${perm} ` + `for action: ${action}`); }; return { getImageData: () => { guard('canvas:read', 'getImageData'); // Return a COPY, not the // original — plugins can't // corrupt state accidentally const orig = getCanvasImageData(); return new ImageData( new Uint8ClampedArray( orig.data), orig.width, orig.height); }, setImageData: (data) => { guard('canvas:write', 'setImageData'); pushHistory(`Plugin: ${pluginId}`); applyImageData(data); }, addToolbarButton: (btn) => { guard('toolbar:add', 'addToolbarButton'); registerPluginButton( pluginId, btn); }, on: (event, handler) => { guard('events:subscribe', 'on'); this.eventBus.on(event, handler); }, storage: { get: async (key) => { guard('storage:read', 'storage.get'); return getPluginStorage( pluginId, key); }, set: async (key, value) => { guard('storage:write', 'storage.set'); return setPluginStorage( pluginId, key, value); }, }, } as PluginAPI; }
Key security principle: plugins receive a COPY of image data, not a reference. Permission checks happen on every API call. Storage is namespaced per plugin — plugins can't read each other's data.
04
Example Plugin: Vintage Filter
// plugins/vintage-filter.ts // Third-party plugin example const VintagePlugin: PixelCraftPlugin = { id: 'vintage-filter', name: 'Vintage Photo Filter', version: '1.0.0', author: 'Community', description: 'Apply a warm vintage look', permissions: [ 'canvas:read', 'canvas:write', 'toolbar:add'], activate(api) { api.addToolbarButton({ id: 'vintage', label: 'Vintage', icon: '📷', onClick: () => { const img = api.getImageData(); const d = img.data; for (let i = 0; i < d.length; i += 4) { // Warm sepia tone const r = d[i], g = d[i+1], b = d[i+2]; d[i] = Math.min(255, r * 1.2 + 30); // red up d[i+1] = Math.min(255, g * 1.0 + 15); // green d[i+2] = Math.max(0, b * 0.8); // blue down } api.setImageData(img); }, }); }, deactivate() { // Cleanup: remove toolbar button }, }; export default VintagePlugin;
05
Plugin Settings UI
function PluginSettings() { const plugins = usePluginStore(s => s.plugins); return ( <section> <h2>Plugins</h2> {plugins.map(p => ( <div key={p.id} className="plugin-card"> <div className="plugin-info"> <h3>{p.name}</h3> <span>v{p.version} by {p.author}</span> <p>{p.description}</p> <div className="perms"> {p.permissions.map(perm => <span key={perm} className="perm-badge"> {perm} </span> )} </div> </div> <button onClick={() => togglePlugin(p.id)}> {p.enabled ? 'Disable' : 'Enable'} </button> </div> ))} <button onClick={openInstallDialog}> + Install Plugin </button> </section> ); }
06
Close the Ticket
git switch -c feature/PIXELCRAFT-103-plugin-system git add src/lib/pluginManager.ts src/types/plugin.ts src/components/ git commit -m "Add plugin architecture with sandboxed API (PIXELCRAFT-103)" git push origin feature/PIXELCRAFT-103-plugin-system # PR → Review → Merge → Close ticket ✅
CS.DEEP-DIVE

Plugin systems are the ultimate expression of the Open/Closed Principle.

Open for extension, closed for modification. Third parties add capabilities without changing your core. This is how the most successful software platforms scale.

// Plugin-based software:

VS Code
  40,000+ extensions
  Core: 3% of functionality
  Extensions: 97% of value

Photoshop
  Plugins since 1991
  Entire industries (3D, HDR)
  built as plugins first

Figma
  Community plugins in iframe sandbox
  No access to main thread
  API message passing only

// The plugin contract:
// 1. Define what you expose (API)
// 2. Sandbox what you don't
// 3. Version the API (semver)
// 4. Never break backward compat
//
// Your API is a product. Plugins
// are your developer community.
"Plugin Lab"
[A]Build a second plugin: "Watermark Stamp" — adds a text watermark to the bottom-right corner of the image. Uses canvas:read, canvas:write, and storage:write (remembers the last watermark text). Test installing both plugins simultaneously.
[B]Add iframe sandboxing: load plugins in an iframe with sandbox="allow-scripts". Communicate via postMessage. The plugin can't access the main window's DOM, cookies, or localStorage. Compare security with the current approach.
[C]Research: how does VS Code's extension API work? How does the Extension Host process isolate extensions? What is the "extension manifest" (package.json contributes)? How does Figma sandbox its plugins in iframes? Write a comparison of plugin sandboxing strategies.
REF.MATERIAL
ARTICLE
Microsoft
The gold standard for plugin APIs: activation events, contribution points, extension manifest. How VS Code enables 40,000+ extensions.
VSCODEPLUGIN APIESSENTIAL
ARTICLE
Figma
Figma's plugin architecture: iframe sandbox, message passing, read/write access to the design file. A modern browser-first plugin model.
FIGMAPLUGIN API
VIDEO
Theo (t3.gg)
How to design plugin systems: API surface, sandboxing, versioning. Lessons from VS Code, Figma, and Obsidian plugin architectures.
PLUGINSDESIGN
ARTICLE
MDN Web Docs
Load JavaScript modules at runtime: import('url'). The foundation for dynamic plugin loading without compile-time dependencies.
DYNAMICOFFICIAL
ARTICLE
Wikipedia
The SOLID principle that plugins embody: software entities should be open for extension but closed for modification. Proposed by Bertrand Meyer in 1988.
SOLIDTHEORY
// LEAVE EXCITED BECAUSE
Third-party plugins install dynamically without redeploying. Sandboxed API: plugins can access the canvas but can't touch auth or database. Permission system, event hooks, scoped storage. You built what makes VS Code, Figma, and Photoshop infinitely extensible.