Overview
Plugins must keep data and assets in platform-managed folders so paths stay portable across Docker, staging, and production. @quan-erp/shared-backend-core exposes AppFolder helpers that resolve process.env paths for you.
- Runtime-generated files → plugin data folder under APP_DATA_FOLDER
- Bundled static files → backend/assets (read-only after install)
- Temp / partial uploads → APP temp folder (may be cleared)
- Never write into the APP_DATA_FOLDER root — always use your plugin subdirectory
1. Application & plugin folders
Four locations matter. Global folders come from env; plugin folders are derived from the plugin name (and version for assets).
| Folder | Path | Utility | Use for |
|---|---|---|---|
| App data (global) | process.env.APP_DATA_FOLDER | AppFolder.getAppDataFolder() | Root for all persistent app data — do not write here directly |
| App temp | process.env.UPLOAD_FILE_TEMP_FOLDER | AppFolder.getAppTempDataFolder() | Temporary files (partial uploads, scratch) — not for durable data |
| Plugin data | <APP_DATA_FOLDER>/<pluginName> | AppFolder.getPluginDataFolder(pluginName) | Runtime-generated data: exports, reports, local files, caches on disk |
| Plugin assets | <INSTALLED_PLUGINS_FOLDER>/<pluginName>/<version>/backend/assets | AppFolder.getPluginAssetFolder(pluginName, version) | Read-only static assets shipped with the plugin package |
2. Data folder vs asset folder
Use the data folder for anything created or updated while the app runs. Use the asset folder only for files you ship with the plugin source and never mutate at runtime.
- Examples for data: generated CSVs, report PDFs, plugin-local SQLite, runtime config files
- Examples for assets: document templates, seed JSON, default icons shipped with the backend
| Asset folder | Data folder | |
|---|---|---|
| Purpose | Static files bundled with the code | Dynamic files created at runtime |
| Persistence | Replaced when the plugin version is reinstalled / updated | Survives plugin updates |
| Utility | getPluginAssetFolder(name, version) | getPluginDataFolder(name) |
| Source in repo | plugins/<name>/backend/assets/ | Created on disk at runtime |
3. AppFolder API
Import AppFolder from @quan-erp/shared-backend-core. Prefer metadata.name and metadata.pluginVersion so paths stay aligned with module.metadata.json.
| Method | Description |
|---|---|
| getAppDataFolder() | Root application data directory (APP_DATA_FOLDER) |
| getAppTempDataFolder() | Temporary upload / scratch directory |
| getPluginDataFolder(pluginName) | Persistent data directory for one plugin |
| getPluginAssetFolder(pluginName, version) | Installed backend/assets for a plugin version |
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 dataDir = AppFolder.getPluginDataFolder(metadata.name);
7const assetDir = AppFolder.getPluginAssetFolder(
8 metadata.name,
9 metadata.pluginVersion,
10);
11
12// Persistent runtime file
13await fs.mkdir(dataDir, { recursive: true });
14await fs.writeFile(path.join(dataDir, "export.csv"), csv, "utf8");
15
16// Bundled template (read-only)
17const template = await fs.readFile(
18 path.join(assetDir, "invoice-template.html"),
19 "utf8",
20);4. Bundling backend assets
Put static backend files under plugins/<name>/backend/assets/. On build / pack, the CLI copies that tree into the plugin bundle so AppFolder.getPluginAssetFolder can resolve them after install.
- Everything under backend/assets/ is packed into available-plugins / installed-plugins
- After Install, resolve with getPluginAssetFolder(metadata.name, metadata.pluginVersion)
- Do not write into the asset folder at runtime — write to getPluginDataFolder instead
1plugins/<plugin-name>/
2└── backend/
3 ├── assets/
4 │ ├── invoice-template.html
5 │ └── seed/
6 │ └── defaults.json
7 └── src/Rules
- Isolation — only write under getPluginDataFolder(yourPluginName)
- Persistence — do not store durable data in getAppTempDataFolder()
- Portability — always use AppFolder; never hardcode /app-data/... paths
- Versioned assets — pass pluginVersion into getPluginAssetFolder so updates stay isolated