Demo

Folder structure

Map of the Quan ERP Node template: repo root, base runtime, plugin source layout, and where watch / install artifacts land.

Mental model

Think in three layers. plugins/ is where you write code. base/available-plugins/ is the staged build. base/backend/installed-plugins/ is what the running ERP actually loads. The quan-erp CLI copies from source → available; the ERP UI copies available → installed.

  • Always run quan-erp from the repo root so plugins/ and base/ resolve correctly
  • Folder name under plugins/ must equal module.metadata.json name
  • Package names: @quan-erp-plugins/<name>-backend and @quan-erp-plugins/<name>-frontend
  • Never import another plugin’s src/ — use shared @quan-erp/* libs or PluginAPI

1. Repository root

Clone of quan-erp-node-template (or your fork). Top-level layout:

  • base/ — platform core you run with Docker Compose; do not put feature business code here
  • plugins/ — every custom module; start from plugins/sample-es
  • .agents/ — Cursor / agent skills and rules for Quark ERP conventions
  • erp — prefer quan-erp watch <name> over hand-copying dist folders
SHworkspace
1my-quan-erp/ # repo root (run quan-erp here) 2├── erp # CLI binary — watch / build / pack / new 3├── .agents/ # Quan ERP skills & rules for AI agents 4├── .gitignore 5├── README.md 6├── base/ # runnable platform core (Docker) 7│ ├── docker-compose.yaml # db, redis, backend, frontend 8│ ├── available-plugins/ # built plugins waiting to install 9│ │ └── <name>/<version>/ 10│ │ ├── backend/ 11│ │ ├── frontend/ 12│ │ └── module.metadata.json 13│ ├── backend/ 14│ │ ├── Dockerfile 15│ │ ├── .env / .env.sample # local secrets (do not commit real values) 16│ │ ├── installed-plugins/ # active plugins served at runtime 17│ │ │ └── <name>/ 18│ │ │ ├── backend/ 19│ │ │ ├── frontend/ 20│ │ │ └── module.metadata.json 21│ │ └── logs/ # optional host-mounted logs 22│ ├── frontend/ # base UI core image / nginx 23│ └── data/ # optional local volumes (db, redis, uploads) 24└── plugins/ # YOUR source code lives here 25 ├── sample-es/ # reference plugin — copy patterns from here 26 └── <my-plugin>/ # one folder per plugin 27 ├── module.metadata.json # name MUST match folder name 28 ├── backend/ 29 └── frontend/

2. base/ — platform core

The base app is a thin runtime: compose services, plugin folders, and host config. Feature ERP screens live in plugins, not in base frontend source.

  • docker-compose.yaml — Postgres (db), Redis, backend (:8080/:8081), frontend (:80)
  • available-plugins/<name>/<version>/ — output of quan-erp watch|build:prod|pack:prod staging
  • backend/installed-plugins/<name>/ — plugins marked installed; backend + browser load from here
  • backend/.env — DATABASE_*, REDIS_*, tokens; keep real secrets out of git
  • frontend/ — base core image / static host; plugins inject UI at runtime
  • data/ — optional bind mounts for db/redis/uploads on local machines

3. plugins/<name>/ — plugin package

One plugin = one folder with metadata + backend package + frontend package.

  • module.metadata.json sits beside backend/ and frontend/ (not inside either package)
  • Keep domain code under feature folders — avoid dumping everything in src/
  • output/ may appear after quan-erp pack:prod (zip archives); safe to gitignore
SHplugin
1plugins/<my-plugin>/ 2├── module.metadata.json # identity + deps + version contract 3├── backend/ 4│ ├── package.json # @quan-erp-plugins/<name>-backend 5│ ├── tsconfig.json 6│ ├── nest-cli / build config # as shipped by the template 7│ └── src/ 8│ ├── index.ts # IPlugin default export → getRootModule() 9│ ├── export.ts # optional: publish for other plugins 10│ ├── <feature>/ # one folder per domain (recommended) 11│ │ ├── *.controller.ts 12│ │ ├── *.service.ts 13│ │ └── dto/ 14│ └── schema/ # TypeORM entities (plugin tables) 15│ └── *.entity.ts 16└── frontend/ 17 ├── package.json # @quan-erp-plugins/<name>-frontend 18 ├── vite.config.ts 19 ├── tsconfig.json 20 ├── index.html 21 └── src/ 22 ├── index.tsx # PluginModule.register(AppRegistry) 23 ├── export.ts # public API for PluginAPI consumers 24 ├── api/<domain>/ # React Query + axios calls 25 ├── page/<feature>/ # screens / dialogs 26 ├── components/ # plugin-local UI pieces 27 ├── hooks/ # plugin-local hooks (optional) 28 ├── store/ # plugin-local Zustand (optional) 29 ├── locales/ # translation dictionaries (optional) 30 └── lib/ 31 ├── axios.ts # setAxiosClient / getAxiosClient 32 └── metadata.ts # typed module.metadata.json

4. module.metadata.json

Identity file read by the CLI, base discovery, and UI install flow.

  • name — unique plugin id; must match plugins/<name>/ folder
  • pluginVersion — folder name under available-plugins/<name>/<version>/
  • requiredBasedVersion — must align with base / @quan-erp/* version line
  • moduleEntryObject — root @Module class name exported from backend entry
  • pluginDependencies — other plugin names that must load first
JSONmodule.metadata.json
1{ 2 "name": "sample-es", 3 "type": "", 4 "pluginVersion": "1.0.0", 5 "description": "Sample plugin", 6 "moduleEntryObject": "Module", 7 "requiredBasedVersion": "1.0.0", 8 "pluginDependencies": {} 9}

5. Backend layout (detail)

Backend is a TypeScript package that default-exports an IPlugin. Controllers, services, DTOs, and entities stay namespaced to the plugin.

  • index.ts — only backend entry the host imports (module.js after build)
  • schema/ — TypeORM entities; prefix table names with the plugin namespace
  • dto/ — validation / transport types; keep controllers thin
  • export.ts — optional publish surface for cross-plugin backend reuse
  • Do not reach into another plugin’s backend/src
SHbackend
1plugins/<name>/backend/src/ 2├── index.ts 3# default-export class implements IPlugin 4# getRootModule() → @Module class 5# getMetadata() → module.metadata.json 6├── export.ts 7# optional — symbols other plugins may consume after publish 8├── <feature>/ # e.g. employee/, leave/, payroll/ 9│ ├── <feature>.controller.ts # @Controller + routes 10│ ├── <feature>.service.ts # @Service business logic 11│ ├── dto/ 12│ │ ├── create-*.dto.ts 13│ │ └── query-*.dto.ts 14│ └── <feature>.module bits # providers registered on root @Module 15└── schema/ 16 ├── <feature>.entity.ts # @Entity — namespace table names 17 └── index.ts # re-export entities for @Module({ entities })

6. Frontend layout (detail)

Frontend is a Vite React app. The core only loads src/index.tsx and calls register(AppRegistry).

  • index.tsx — menus, routes, dashboards, settings, reports, PluginAPI.expose, …
  • api/ — all HTTP + React Query; pages import hooks, not raw axios sprawl
  • page/ — route-level screens; keep feature files together
  • lib/axios.ts — setAxiosClient(AppRegistry.getAxiosClient()) inside register()
  • lib/metadata.ts — read name/version/deps from module.metadata.json
  • export.ts — only stable selectors / hooks / types other plugins may use
SHfrontend
1plugins/<name>/frontend/src/ 2├── index.tsx 3# default-export PluginModule 4# register(AppRegistry): wire axios, menus, routes, dashboards, … 5├── export.ts 6# stable public surface for PluginAPI.expose / consumers 7├── api/ 8│ └── <domain>/ 9│ ├── <domain>.api.ts # axios + React Query hooks 10│ └── types.ts # request/response types 11├── page/ 12│ └── <feature>/ 13│ ├── <feature>.page.tsx # route screen 14│ ├── <feature>-form.tsx 15│ └── columns.tsx # table defs (optional) 16├── components/ # shared inside this plugin only 17├── hooks/ 18├── store/ 19├── locales/ 20│ ├── en.json 21│ └── mm.json 22└── lib/ 23 ├── axios.ts # host client bridge 24 └── metadata.ts # name / version / dependencies

7. Build & install artifact paths

Source, available, and installed are different directories. If the UI is blank after install, check installed-plugins and the module table — not only plugins/.

  • quan-erp watch|build:prod → writes base/available-plugins/<name>/<version>/
  • ERP UI Install → copies into base/backend/installed-plugins/<name>/
  • If already installed, watch also refreshes the installed copy during development
  • quan-erp pack:prod → zip under plugins/<name>/output (or a path you pass)
SHartifacts
1# Source (edit here) 2plugins/<name>/{backend,frontend,module.metadata.json} 3 4# After quan-erp watch|build:prod → staged for install 5base/available-plugins/<name>/<version>/ 6├── backend/ # compiled backend dist 7├── frontend/ # Vite build output 8└── module.metadata.json 9 10# After ERP UI Install → active at runtime 11base/backend/installed-plugins/<name>/ 12├── backend/ 13├── frontend/ 14└── module.metadata.json

8. Layout rules

  • No cross-plugin direct imports from another plugin’s src/
  • Namespace DB tables, API routes, menu keys, and registry ids with the plugin name
  • Keep API logic in src/api — not inside page components
  • Do not edit base/available-plugins or installed-plugins by hand — use quan-erp CLI + UI install
  • Align @quan-erp/* versions in both backend and frontend package.json with the base images
  • Treat sample-es as the canonical layout reference when unsure