Demo

Plugins frontend and backend assets

Ship static files with the plugin: backend assets under backend/assets/ via AppFolder; frontend asset URLs via PluginAssets.network. Never hardcode APP_DATA_FOLDER; never write into the asset folder at runtime.

Goal & when

Use bundled assets for templates, seed JSON, logos, and other read-only files that travel with the plugin package. Use the plugin data folder for anything created or updated while the app runs.

  • Backend read-only — plugins/<name>/backend/assets/
  • Frontend URLs — PluginAssets.network(metadata, relativePath)
  • Runtime writes — AppFolder.getPluginDataFolder(metadata.name), not the asset folder

1. Prerequisites

You need a plugin with matching metadata.name / pluginVersion and access to the shared AppFolder / PluginAssets helpers.

  • module.metadata.json — name and pluginVersion align with the installed bundle
  • Backend — @quan-erp/shared-backend-core (AppFolder)
  • Frontend — @quan-erp/shared-frontend-core (PluginAssets)
  • Rebuild + reinstall (or watch refresh) after changing packed assets

2. Backend assets

Put read-only files under plugins/<name>/backend/assets/. The CLI packs them into available/installed plugins. Resolve paths with AppFolder — never hardcode APP_DATA_FOLDER.

  • Source — plugins/<name>/backend/assets/…
  • Runtime path — AppFolder.getPluginAssetFolder(metadata.name, metadata.pluginVersion)
  • Writable runtime files — AppFolder.getPluginDataFolder(metadata.name) instead
  • Do not write into the asset folder after install — it is versioned and replaced on upgrade
SHsource layout
1plugins/<plugin-name>/ 2└── backend/ 3 ├── assets/ 4 │ ├── invoice-template.html 5 │ └── seed/ 6 │ └── defaults.json 7 └── src/
TSXread bundled file
1import { AppFolder } from "@quan-erp/shared-backend-core"; 2import metadata from "../../module.metadata.json" with { type: "json" }; 3import fs from "node:fs/promises"; 4import path from "node:path"; 5 6const assetDir = AppFolder.getPluginAssetFolder( 7 metadata.name, 8 metadata.pluginVersion, 9); 10const template = await fs.readFile( 11 path.join(assetDir, "invoice-template.html"), 12 "utf8", 13);
TSXwrite runtime data
const dataDir = AppFolder.getPluginDataFolder(metadata.name); await fs.mkdir(dataDir, { recursive: true }); await fs.writeFile(path.join(dataDir, "export.csv"), csv, "utf8");

3. Frontend assets

Use PluginAssets.network(metadata, path) from @quan-erp/shared-frontend-core to build absolute URLs for bundled plugin assets in the UI. Keep the relative path stable and aligned with what you ship in the package.

  • Import PluginAssets from @quan-erp/shared-frontend-core
  • Pass the same module.metadata.json the plugin uses elsewhere
  • path is relative to the plugin’s packed frontend / asset tree your stack exposes
TSXPluginAssets
1import { PluginAssets } from "@quan-erp/shared-frontend-core"; 2import metadata from "../module.metadata.json" with { type: "json" }; 3 4export function PluginLogo() { 5 const src = PluginAssets.network(metadata, "images/logo.png"); 6 return <img src={src} alt={metadata.name} />; 7}

4. Verify & rules

  • After watch / build, assets exist under available-plugins (and installed-plugins after Install)
  • getPluginAssetFolder resolves and readFile succeeds for a known relative path
  • PluginAssets.network returns a loadable URL in the browser
  • Assets are versioned with the plugin package — rebuild + reinstall after changes
  • Durable generated files belong in getPluginDataFolder, not assets
  • Keep relative paths stable so backend AppFolder and frontend PluginAssets stay aligned
  • Isolation — only write under getPluginDataFolder(yourPluginName); never hardcode /app-data/…

Common mistakes

  • Hardcoding APP_DATA_FOLDER or /app-data/... paths instead of AppFolder
  • Writing exports / reports into the asset folder at runtime
  • Passing the wrong pluginVersion into getPluginAssetFolder after an upgrade
  • Changing asset files without rebuild / reinstall (or watch refresh)
  • Frontend path that does not match the packed asset relative path
  • Storing durable data in the app temp folder