Skip to content

Architecture Overview

“ERP must adapt to the business — not the other way around.”

iZ ERP is inspired by WordPress’s plugin/theme architecture, applied to business operations. The core is intentionally minimal — everything else is a module you enable, disable, or build yourself.


┌──────────────────────────────────────────────────────────────┐
│ COMMUNITY EXTENSIONS (Tier 3) │
│ automation · document-hub · sync-connectors · billing │
├──────────────────────────────────────────────────────────────┤
│ PLATFORM MODULES (Tier 2) │
│ global-search · import · api-keys · webhooks · settings │
├──────────────────────────────────────────────────────────────┤
│ CRM CORE (Tier 1) │
│ contacts · deals · companies · activities · custom-fields │
├──────────────────────────────────────────────────────────────┤
│ CORE ENGINE (Tier 0 — IMMUTABLE) │
│ schema · event-bus · rbac · auth · db · migrations │
└──────────────────────────────────────────────────────────────┘
TierLoadedCan be disabled
0 — Core EngineAlways❌ Never
1 — CRM CoreAlways❌ No
2 — Platform ModulesConfigurable✅ Yes
3 — Community ExtensionsOpt-in✅ Yes

These rules are non-negotiable. All code in izhubs — including extensions — must follow them.

RuleWhat it means
No direct DB accessOnly core/engine/*.ts may call db.query(). Routes import from engine.
ApiResponse alwaysAll routes use ApiResponse.success/error. Never NextResponse.json() directly.
Zod on DB outputEvery DB row passes through Schema.parse() before returning.
Soft-delete onlyNever DELETE FROM. Use UPDATE SET deleted_at = NOW().
withPermission() guardEvery API route wrapped with withPermission('resource:action', handler).
No component > 150 linesSplit if exceeded.
Sequential migrationsNew migration = new file 00X_description.sql. Never edit committed migrations.
EventBus for cross-moduleModules never import each other. Communication = eventBus.emit() only.

iZ ERP uses an internal EventBus (inspired by WordPress do_action):

// Emitting an event (inside engine)
await eventBus.emit('deal.stage_changed', { deal, fromStage, toStage });
// Listening to an event (in a module or extension)
eventBus.on('deal.stage_changed', async ({ deal }) => {
// send notification, trigger automation, etc.
});

Built-in events:

EventEmitted when
deal.createdNew deal created
deal.stage_changedDeal moved to a different stage
deal.wonDeal stage set to won
deal.lostDeal stage set to lost
contact.createdNew contact created
contact.updatedContact fields updated

izhubs-erp/
├── .agent/ ← AI context (memory, tracks, skills)
├── core/
│ ├── schema/ ← Zod schemas — source of truth
│ └── engine/ ← DB access layer (only place db.query() is allowed)
├── modules/ ← crm, automation, contracts...
├── extensions/sdk/ ← ExtensionBase.ts for building add-ons
├── app/ ← Next.js frontend + API routes
├── database/migrations/ ← Sequential SQL migration files
└── tests/contracts/ ← Schema + API contract tests