Skip to main content

Plugin Package Architecture - Quick Reference

Visual Architecture Diagram

┌─────────────────────────────────────────────────────────────────┐
│ EXTERNAL DEPENDENCIES │
│ @refinedev/*, react, react-router-dom, @tanstack/react-query │
└────────────────────────────┬────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│ @framework-m/desk (CORE UI LIBRARY) │
│ 📦 Published to GitLab npm: @framework-m/desk@0.1.0 │
│ 📁 Location: libs/framework-m-desk/ │
├─────────────────────────────────────────────────────────────────┤
│ Responsibilities: │
│ • Refine.dev data provider (REST API) │
│ • Auth provider (JWT, sessions) │
│ • Live provider (WebSocket) │
│ • Base components: <ListView>, <FormView>, <ShowView> │
│ • Hooks: useDocType(), useMetadata(), useWorkflow() │
│ • Utilities: formatDate(), validateForm(), etc. │
├─────────────────────────────────────────────────────────────────┤
│ Who uses it: │
│ ✓ Shell application (frontend/) │
│ ✓ All app plugins (apps/*/frontend/) │
│ ✓ Any Framework M project │
├─────────────────────────────────────────────────────────────────┤
│ Breaking changes? NO - Existing users unaffected │
└────────────────────────────┬────────────────────────────────────┘

┌────────────┴──────────────┐
│ │
▼ ▼
┌───────────────────────────┐ ┌──────────────────────────────────┐
│ @framework-m/plugin-sdk │ │ App Plugins (Workspace) │
│ (PLUGIN INFRASTRUCTURE) │ │ NOT published to npm │
│ │ │ │
│ 📦 Published: GitLab npm │ │ Example: @my-company/wms │
│ 📁 libs/plugin-sdk/ │ │ 📁 apps/wms/frontend/ │
├───────────────────────────┤ ├──────────────────────────────────┤
│ Exports: │ │ Structure: │
│ • PluginRegistry │ │ • plugin.config.ts │
│ • ServiceContainer │ │ • package.json (framework-m) │
│ • usePluginMenu() │ │ • src/pages/ │
│ • useService() │ │ • src/components/ │
│ • usePlugin() │ │ • src/services/ │
│ • FrameworkMPlugin type │ ├──────────────────────────────────┤
│ • MenuItem type │ │ Defines: │
├───────────────────────────┤ │ • Menu items │
│ Used by: │ │ • Routes │
│ ✓ Shell app │ │ • Services │
│ ✓ Vite plugin │ │ • Providers │
│ ✓ App plugins │ ├──────────────────────────────────┤
└───────────────────────────┘ │ Examples: │
│ │ • apps/wms/frontend/ │
│ │ • apps/personnel/frontend/ │
│ │ • apps/finance/frontend/ │
│ └──────────────────────────────────┘
│ │
│ │
▼ │
┌───────────────────────────────────────┐ │
│ @framework-m/vite-plugin │ │
│ (BUILD-TIME TOOLING) │ │
│ │ │
│ 📦 Published: GitLab npm │ │
│ 📁 libs/vite-plugin/ │ │
├───────────────────────────────────────┤ │
│ Responsibilities: │ │
│ • Scan workspace for plugin packages │ │
│ • Generate virtual module │ │
│ • Configure code-splitting │ │
│ • Setup HMR for plugins │ │
├───────────────────────────────────────┤ │
│ Used by: │ │
│ ✓ Shell app (vite.config.ts only) │ │
└───────────────┬───────────────────────┘ │
│ │
│ discovers │ imports
│ │
▼ │
┌─────────────────────────────────────────────┴───────────────────┐
│ frontend/ (SHELL APPLICATION) │
│ NOT published - Static build output │
│ 📁 frontend/ │
├──────────────────────────────────────────────────────────────────┤
│ Responsibilities: │
│ • Bootstrap PluginRegistry │
│ • Compose all plugins into single UI │
│ • Provide base layout (Sidebar, Header) │
│ • Handle auth flow │
│ • Error boundaries │
├──────────────────────────────────────────────────────────────────┤
│ Dependencies: │
│ • @framework-m/desk ^0.1.0 │
│ • @framework-m/plugin-sdk ^0.1.0 │
│ • @framework-m/vite-plugin ^0.1.0 (devDependency) │
│ • Workspace plugins (apps/*/frontend/) via symlinks │
├──────────────────────────────────────────────────────────────────┤
│ Build Output (frontend/dist/): │
│ • index.html │
│ • assets/main-[hash].js (shell + framework) │
│ • assets/wms-[hash].js (WMS plugin - lazy) │
│ • assets/personnel-[hash].js (Personnel plugin - lazy) │
│ │
│ Deployed as: │
│ • Python package (m build → pip install) │
│ • CDN (aws s3 sync dist/ s3://bucket/) │
│ • Container (COPY dist/ /app/static) │
└──────────────────────────────────────────────────────────────────┘

Data Flow: How Plugin Menus Work

┌─────────────────────────────────────────────────────────────────┐
│ Step 1: Plugin Definition │
│ apps/wms/frontend/plugin.config.ts │
├──────────────────────────────────────────────────────────────────┤
│ export default { │
│ name: 'wms', │
│ menu: [ │
│ { │
│ name: 'wms.warehouse', │
│ label: 'Warehouse', │
│ route: '/doctypes/wms.warehouse', │
│ module: 'Inventory', │
│ } │
│ ] │
│ } satisfies FrameworkMPlugin │
└────────────────────────────┬─────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│ Step 2: Build-Time Discovery │
│ @framework-m/vite-plugin scans workspace │
├──────────────────────────────────────────────────────────────────┤
│ 1. Find all package.json files │
│ 2. Filter those with "framework-m" metadata │
│ 3. Collect plugin.config.ts paths │
│ 4. Generate virtual module: │
│ │
│ // virtual:framework-m-plugins │
│ import wms from 'apps/wms/frontend/dist/plugin.config.js'; │
│ import personnel from 'apps/personnel/.../plugin.config.js'; │
│ export default [wms, personnel]; │
└────────────────────────────┬─────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│ Step 3: Runtime Registration │
│ frontend/src/App.tsx bootstrap() │
├──────────────────────────────────────────────────────────────────┤
│ import plugins from 'virtual:framework-m-plugins'; │
│ │
│ const registry = new PluginRegistry(); │
│ for (const plugin of plugins) { │
│ await registry.register(plugin); │
│ } │
│ │
│ // Registry now contains: │
│ // - wms.menu = [{name: 'wms.warehouse', ...}] │
│ // - personnel.menu = [...] │
└────────────────────────────┬─────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│ Step 4: Menu Merging │
│ PluginRegistry.getMenu() │
├──────────────────────────────────────────────────────────────────┤
│ 1. Collect all menu items from all plugins │
│ 2. Group by module: │
│ - Inventory: [wms.warehouse, wms.bin_location, ...] │
│ - HR: [personnel.employee, ...] │
│ 3. Sort by order │
│ 4. Return merged tree: │
│ │
│ [ │
│ { │
│ name: 'inventory', │
│ label: 'Inventory', │
│ children: [ │
│ { name: 'wms.warehouse', label: 'Warehouse' }, │
│ { name: 'wms.bin_location', label: 'Bin Location' } │
│ ] │
│ }, │
│ { name: 'hr', label: 'HR', children: [...] } │
│ ] │
└────────────────────────────┬─────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│ Step 5: UI Rendering │
│ frontend/src/layout/Sidebar.tsx │
├──────────────────────────────────────────────────────────────────┤
│ function Sidebar() { │
│ const pluginMenu = usePluginMenu(); // Hook from plugin-sdk │
│ │
│ return ( │
│ <nav> │
│ {pluginMenu.map(moduleGroup => ( │
│ <ModuleSection key={moduleGroup.name}> │
│ <Icon name={moduleGroup.icon} /> │
│ {moduleGroup.label} │
│ {moduleGroup.children.map(item => ( │
│ <MenuItem │
│ route={item.route} │
│ label={item.label} │
│ /> │
│ ))} │
│ </ModuleSection> │
│ ))} │
│ </nav> │
│ ) │
│ } │
└──────────────────────────────────────────────────────────────────┘

Package Dependency Chain

Level 0 (External):
react, react-dom, @refinedev/*, @tanstack/react-query


Level 1 (Core):
@framework-m/desk

├──────────────────┐
│ │
▼ ▼
Level 2 (Plugin System):
@framework-m/plugin-sdk App Plugins (@my-company/wms)
│ │
├─────────────────────────┤
│ │
▼ │
Level 3 (Build Tools):
@framework-m/vite-plugin │
│ │
└────────┬────────────────┘


Level 4 (Shell):
frontend/ (Shell App)


Level 5 (Build Output):
frontend/dist/ (Static Assets)

Workspace Structure Example

my-erp-project/
├── pnpm-workspace.yaml
├── package.json

├── frontend/ # Shell Application
│ ├── package.json
│ │ └── dependencies:
│ │ ├── @framework-m/desk: ^0.1.0
│ │ └── @framework-m/plugin-sdk: ^0.1.0
│ ├── vite.config.ts
│ │ └── plugins: [frameworkMPlugin()]
│ ├── src/
│ │ ├── App.tsx # Bootstrap plugins
│ │ └── layout/Sidebar.tsx # Uses usePluginMenu()
│ └── dist/ # Build output

├── apps/
│ ├── wms/
│ │ ├── backend/ # Python app
│ │ │ └── pyproject.toml
│ │ └── frontend/ # WMS Plugin
│ │ ├── package.json
│ │ │ ├── name: @my-company/wms
│ │ │ ├── private: true
│ │ │ ├── framework-m:
│ │ │ │ └── plugin: ./dist/plugin.config.js
│ │ │ └── dependencies:
│ │ │ ├── @framework-m/desk: ^0.1.0
│ │ │ └── @framework-m/plugin-sdk: ^0.1.0
│ │ ├── plugin.config.ts # Menu, routes, services
│ │ └── src/
│ │ ├── pages/
│ │ ├── components/
│ │ └── services/
│ │
│ └── personnel/
│ ├── backend/
│ └── frontend/ # Personnel Plugin
│ ├── package.json
│ ├── plugin.config.ts
│ └── src/

└── libs/ # Framework packages
├── framework-m-desk/ # Published to npm
│ ├── package.json
│ │ └── name: @framework-m/desk
│ └── src/
├── framework-m-plugin-sdk/ # Published to npm
│ ├── package.json
│ │ └── name: @framework-m/plugin-sdk
│ └── src/
└── framework-m-vite-plugin/ # Published to npm
├── package.json
│ └── name: @framework-m/vite-plugin
└── src/

Package Publishing Matrix

PackageLocationPublished?RegistryVersioning
@framework-m/desklibs/framework-m-desk/✅ YesGitLab npmFollows Python
@framework-m/plugin-sdklibs/framework-m-plugin-sdk/✅ YesGitLab npmIndependent
@framework-m/vite-pluginlibs/framework-m-vite-plugin/✅ YesGitLab npmIndependent
frontend/ (Shell)frontend/❌ No--
@my-company/wmsapps/wms/frontend/❌ NeverWorkspacePrivate
@my-company/personnelapps/personnel/frontend/❌ NeverWorkspacePrivate

When to Use Each Package

Use @framework-m/desk when:

  • ✅ Building any Framework M frontend (single or multi-app)
  • ✅ Need data provider for REST API
  • ✅ Need auth provider
  • ✅ Need base DocType components
  • ✅ Want to use Framework M hooks

Use @framework-m/plugin-sdk when:

  • ✅ Building multi-app project with plugins
  • ✅ Need to access plugin menus/routes
  • ✅ Want to register services
  • ✅ Creating reusable app modules

Use @framework-m/vite-plugin when:

  • ✅ Setting up shell app build
  • ✅ Need auto-discovery of plugins
  • ✅ Want code-splitting per plugin
  • ✅ Building multi-app project

Don't use plugins if:

  • ❌ Single-app project (just use @framework-m/desk directly)
  • ❌ Simple customization (add pages directly to frontend/src/)
  • ❌ Prototyping (overhead not worth it)

Migration Path

Existing Single-App Projects (No Changes Needed)

// Current setup - Still works!
{
"name": "my-app-frontend",
"dependencies": {
"@refinedev/core": "^5.0.0",
"react": "^19.0.0"
}
}

// Add desk when ready (optional)
{
"dependencies": {
"@framework-m/desk": "^0.1.0", // ← Just add this
"react": "^19.0.0"
}
}

New Multi-App Projects

// Shell app
{
"name": "frontend",
"dependencies": {
"@framework-m/desk": "^0.1.0",
"@framework-m/plugin-sdk": "^0.1.0"
},
"devDependencies": {
"@framework-m/vite-plugin": "^0.1.0"
}
}

// Plugin app (workspace package)
{
"name": "@my-company/wms",
"private": true,
"framework-m": {
"plugin": "./dist/plugin.config.js"
},
"dependencies": {
"@framework-m/desk": "workspace:^0.1.0",
"@framework-m/plugin-sdk": "workspace:^0.1.0"
}
}

Quick Decision Tree

Are you building a single app?
├─ Yes → Just use @framework-m/desk
│ No plugin system needed

└─ No (multi-app) → Use plugin system

├─ Shell app needs:
│ ├─ @framework-m/desk
│ ├─ @framework-m/plugin-sdk
│ └─ @framework-m/vite-plugin (devDependency)

└─ Plugin apps need:
├─ @framework-m/desk
└─ @framework-m/plugin-sdk