Launch OpenGPEX

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: usePluginSelfConfig is for plugin-private settings. Editor-wide user preferences (snap sensitivity, theme, filtering toggles) belong in the central PresetsFactory (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