Launch OpenGPEX

Asset Management

How plugins load and serve static assets (images, icons, data files), and how the v2 document asset store relates to plugin resources.


Two Kinds of "Assets"

Kind Stored in Accessed via Lifetime
Plugin resources (shipped files) Plugin directory usePluginResource(path) Installed with the plugin
Document assets (user content) Content-addressed asset store AssetService (ctx.assets) Tied to the open document

Plugin resources are static files you ship inside your plugin ZIP. Document assets are runtime content — layers, bakes, imported files — managed by the engine's sharded store. Do not confuse the two APIs.


usePluginResource Hook

import { usePluginResource } from '@opengpex/editor/core/context';

function MyPanel() {
  const logoUrl = usePluginResource('visuals/logo.svg');
  const presetsUrl = usePluginResource('data/presets.json');
  return <img src={logoUrl} alt="Logo" />;
}

How It Works

The hook resolves to /api/plugins/serve/${_folderName}/${relativePath}. The serve API searches in order:

  1. Dynamic data directory: data/plugins/user/${folderName}/${path}
  2. Static source directory: src/lib/opengpex/plugins/user/${folderName}/${path}

This dual fallback means identical code works in development (Track 1) and production (Track 2) with zero configuration. The same proxy also serves the plugin's compiled bundle itself (dist/index.js) to the dynamic loader.


Supported Asset Types

The serve API infers MIME types for these formats (anything else falls back to application/octet-stream):

Type Extensions Use Case
Code/Styles .js, .mjs, .css The plugin bundle itself, stylesheets
Images .png, .jpg, .jpeg, .gif, .webp, .avif, .svg, .ico Icons, thumbnails, overlays
Data .json Configuration, presets
Fonts .woff2, .woff, .ttf, .otf Custom typography
Documents .pdf Help docs
Media .mp3, .mp4 Sound/video (rare)
Binary .wasm WASM modules

Directory Naming

Asset directories may use any name — the system does not restrict it:

MyPlugin/
├── index.tsx
├── components.tsx
├── visuals/         # any name works
│   └── logo.svg
├── icons/
│   └── tool.png
└── data/
    └── presets.json

All subdirectories are automatically collected by pnpm pack-plugin.


Document Assets via AssetService (v2)

When your plugin produces or consumes document content, use the content-addressed store:

execute: async (ctx) => {
  // Import: registers a page and its source file, deriving IDs from the content hash
  const record = await ctx.assets.storeBundle(page, sourceBlob);

  // Display: live blob URL for <img> / <canvas> consumers
  const url = ctx.assets.getURL(record.id);

  // Decoded high-depth pixels for analysis (Uint16Array | Float32Array, or null)
  const pixels = await ctx.assets.getDec(record.id);

  // Original encoded file (follows fileHashOf)
  const file = await ctx.assets.getRaw(record.id);
}

Key points:

  • Asset IDs are hash-derived: ${hash}#N for multi-page imports, bare ${hash} for bake results, raw:${hash} for original sources.
  • assets.sweep(activeIds) performs garbage collection; the engine calls it on frame deletion and periodically. Do not hold long-lived references to blob: URLs of swept assets.
  • Zero-sized bitmaps are rejected — the store skips and logs rather than feeding invalid textures to WebGPU.

Security

  • Path traversal (../) in resource paths is blocked by the serve API
  • Plugins can only read files inside their own directory
  • No access to host system files or other plugins' resources

Next Steps


Last updated: 2026-10-12