API Reference
Complete reference for all hooks, context services, and APIs available to OpenGPEX v2 plugin developers.
Core Hooks
All hooks are imported from @opengpex/editor/core/context:
import {
useEditorServices,
useEditorState,
usePluginCommands,
usePluginSignals,
usePluginSelfConfig,
usePluginConfig,
usePluginSelfBusy,
usePluginResource,
usePluginList,
useVolatileInteraction,
useFastSync,
} from '@opengpex/editor/core/context';
useEditorServices()
Returns static, stable service references. Never triggers re-renders.
const { actions, pixels, geometry, layers, assets, clipboard, plugins, volatileRef, coreVersion } = useEditorServices();
| Property | Type | Description |
|---|---|---|
actions |
EditorActions |
Command execution, state mutations, adv facade |
pixels |
PixelService |
Bitmap decode, rasterize, filter — dispatched to the core Worker |
geometry |
GeometryService |
Coordinate transforms, bounding boxes, snap alignment |
layers |
LayerService |
Layer CRUD with safety checks |
assets |
AssetService |
Content-addressed asset store (v2 API below) |
clipboard |
ClipboardService |
Clipboard read/write |
plugins |
PluginService |
Plugin/command/shortcut registry queries |
volatileRef |
React.RefObject<VolatileState> |
Direct mutable ref for 60fps fast-track state |
coreVersion |
string |
Current engine version |
actions.executeCommand(id, payload?)
Dispatches a command by UID. Within a plugin context, short IDs auto-resolve:
// Within plugin scope — short ID auto-resolves to the full UID
actions.executeCommand('cmd.apply_color', { color: '#ff0000' });
// Cross-plugin — must use the full UID
actions.executeCommand('opengpex.drawers.image_info.cmd.download');
actions.adv.* — System Advanced Facade
For system-level operations, always use the strongly-typed actions.adv facade instead of raw command ID strings:
const { actions } = useEditorServices();
// Viewport
actions.adv.viewport.transform.rotate.execute({ direction: 'left' });
actions.adv.viewport.translate.fit.execute();
// Frame
await actions.adv.frame.create.trunk.execute({ source: file });
await actions.adv.frame.resize.resample.execute({ targetDim: { w: 1920, h: 1080 } });
// Layer
actions.adv.layer.merge.down.execute();
await actions.adv.layer.mask.toggle.execute({ layerId, maskId });
// System
await actions.adv.system.assets.sync.execute();
Each adv node exposes .execute(payload?), .name, .shortcutLabel, and .id.
useEditorState()
Subscribes to reactive editor state. Re-renders when subscribed values change.
const { state, activeFrame, activeLayer, getSignal } = useEditorState();
| Property | Type | Description |
|---|---|---|
state |
EditorState |
Full state (frames, layers, pluginConfig, interaction) |
activeFrame |
Frame | null |
Currently active frame/artboard |
activeLayer |
Layer | null |
Currently active layer |
getSignal(key, default?) |
function | Read signal (auto-prefixes the plugin UID in plugin scope) |
Only destructure the leaf fields you need — subscribing to the entire state object causes excessive re-renders.
usePluginCommands()
Returns all commands registered by the current plugin, mapped to camelCase + Cmd suffix keys.
const { applyColorCmd, resetAllCmd } = usePluginCommands();
applyColorCmd.execute({ color: '#ff0000' }); // Execute with payload
applyColorCmd.name; // Human-readable name
applyColorCmd.shortcutLabel; // e.g. "⌘Z"
Pass a generated CommandsMap type for full compile-time typing:
import type { ColorPickerCommandsMap } from './commands.d';
const { applyColorCmd } = usePluginCommands<ColorPickerCommandsMap>();
// applyColorCmd.execute payload is precisely typed
Regenerate commands.d.ts after adding or renaming commands:
pnpm gen-plugin-types src/lib/opengpex/plugins/user/ColorPickerDrawer
usePluginSignals()
Returns all signals registered by the current plugin, mapped to camelCase + Signal suffix keys.
const { activeToolSignal } = usePluginSignals<MySignalsMap>();
activeToolSignal.value; // read (reactive)
activeToolSignal.set('eraser'); // write
usePluginSelfConfig()
Reads and updates the current plugin's configuration. Zero arguments — the scope is resolved from PluginContext.
const [config, setConfig] = usePluginSelfConfig<ColorPickerConfig>();
setConfig({ currentColor: '#00ff00' }); // merge-patch
Note:
usePluginSelfConfigis for plugin-private settings. Editor-wide user preferences (snap sensitivity, theme, filtering toggles) belong in the centralPresetsFactory(presets.get()/usePreset()), not in per-plugin config.
usePluginConfig(pluginId)
Reads another plugin's configuration by its full UID:
const [otherConfig] = usePluginConfig<OtherConfig>('opengpex.drawers.adjustments');
Modifying another plugin's config directly is discouraged — prefer the Command Bus.
usePluginSelfBusy()
Returns the reactive busy state of the current plugin:
const isBusy = usePluginSelfBusy();
// true while ctx.scoped.setBusy(true) is in effect
While busy, the sidebar icon automatically plays a rainbow-gradient breathing animation, even when the panel is closed.
usePluginResource(relativePath)
Resolves a static asset bundled with the plugin:
const logoUrl = usePluginResource('visuals/logo.svg');
Resolves to /api/plugins/serve/${_folderName}/${relativePath}. Works identically on both loading tracks. See Asset Management.
usePluginList()
Returns a reactive snapshot of all registered plugins (for building management UIs). Re-renders when plugins are registered, unregistered, or toggled.
useVolatileInteraction(key)
Subscribes to a single high-frequency interaction field without global re-renders:
const hoveredLayerId = useVolatileInteraction('hoveredLayerId');
const cursor = useVolatileInteraction('cursorOverride');
useFastSync(callback)
Intercepts the fast-track (60Hz) stream for drag/zoom-synced UI without React latency:
import { useFastSync } from '@opengpex/editor/core/state/volatile';
useFastSync(syncRef, !!activeFrame, (volatile, frame, camera) => {
setDisplayK(camera.k); // frame-accurate, no stale closure
});
Command Context (ctx in execute)
execute: (ctx: EditorContextValue, payload?: P) => {
// Core services (same objects as useEditorServices)
ctx.actions // State mutations, command dispatch
ctx.layers // Layer CRUD (prefer over raw actions)
ctx.geometry // Coordinate transforms
ctx.pixels // Pixel operations (core Worker bridge)
ctx.assets // Asset store (v2 API)
ctx.clipboard // Clipboard
ctx.plugins // Registry queries
// Plugin-scoped namespace (present only for plugin-owned commands)
ctx.scoped!.selfConfig
ctx.scoped!.setSelfConfig(patch)
ctx.scoped!.getSignal(key, default)
ctx.scoped!.setSignal(key, value)
ctx.scoped!.toggleSignal(key)
ctx.scoped!.setBusy(boolean)
}
Prefer ctx.layers over ctx.actions
| Method | Benefit over raw actions |
|---|---|
ctx.layers.addLayer(frameId, layer) |
Auto-expands composite layers, validates structure |
ctx.layers.activate(frameId, layerId) |
Guards against activating non-activatable layers |
ctx.layers.removeLayers(frameId, ids) |
Cascade-deletes children, migrates focus |
AssetService (v2)
The v2 asset store is content-addressed. Asset IDs derive from a hash of the source blob:
| Scenario | Asset ID |
|---|---|
| Single image import | ${hash}#0 |
| Multi-page import, page N | ${hash}#N |
| Render bake result | ${hash} (no page suffix) |
| Original file source | raw:${hash} |
| Intent | Call | Notes |
|---|---|---|
| Import a page + source file | await assets.storeBundle(page, sourceBlob) |
Preferred entry point; derives IDs internally |
| Inject a bake result | assets.inject(assetId, blob, options) |
Custom hash, no # |
| Read light record | assets.get(assetId) |
Synchronous, from the in-memory pool |
| Get display URL | assets.getURL(assetId) |
Live blob: URL |
| Read high-precision pixels | assets.getDec(assetId) |
TypedArray, exact page |
| Read encoded source file | assets.getRaw(assetId) |
Follows fileHashOf automatically |
| Trigger garbage collection | assets.sweep(activeIds) |
Frees unreferenced records |
// Typical usage inside a command
execute: async (ctx) => {
const record = await ctx.assets.storeBundle(page, sourceBlob);
const url = ctx.assets.getURL(record.assetId); // <img src={url}>
}
WebGPU hard-rejects zero-sized textures. Never register or inject blobs with zero dimensions — the store skips and warns rather than poisoning the GPU device.
Performance Summary
| Hook | Re-render Behavior |
|---|---|
useEditorServices() |
Never — purely static references |
useEditorState() |
On state changes (scope via destructuring) |
usePluginCommands() |
Only when plugin scope changes (rare) |
usePluginSignals() |
When any plugin signal changes |
usePluginSelfConfig() |
When this plugin's config changes |
usePluginSelfBusy() |
When busy state changes |
For 60fps interactions, never dispatch state updates in pointer handlers — write to volatileRef.current directly and update the DOM imperatively.
Last updated: 2026-10-12