Demo

Plugin app data

Where plugins store persistent runtime files and read bundled backend assets. Use AppFolder from @quan-erp/shared-backend-core — never hardcode APP_DATA_FOLDER paths.

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).

FolderPathUtilityUse for
App data (global)process.env.APP_DATA_FOLDERAppFolder.getAppDataFolder()Root for all persistent app data — do not write here directly
App tempprocess.env.UPLOAD_FILE_TEMP_FOLDERAppFolder.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/assetsAppFolder.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 folderData folder
PurposeStatic files bundled with the codeDynamic files created at runtime
PersistenceReplaced when the plugin version is reinstalled / updatedSurvives plugin updates
UtilitygetPluginAssetFolder(name, version)getPluginDataFolder(name)
Source in repoplugins/<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.

MethodDescription
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
TSXresolve paths
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
SHsource layout
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