Module Development — Developer Guide
Canonical checklist for building a business module. Copy the Leads module structure; do not invent a second pattern.
Registration recipe
- Catalog (production) — Ship an idempotent data migration that calls
App\Support\Catalog\DefaultModuleRegistrar(slug, pricing defaults,status=published,is_default_included/is_billable). Also keepCatalogSeederin sync for local/CI fresh DBs only — never rely ondb:seedin production. Optional: Central Modules API for non-default / commercial catalog edits. - Permissions (production) — Add
{slug} => [view, create, update, delete, …]inconfig/tenant-permissions.phpand default grants inconfig/tenant-default-role-permissions.php. Ship a data migration that callsTenantPermissionSynchronizer::grantMissingDefaultRolePermissions([...])so existing workspaces receive grants additively. Modules never auto-grant permissions; roles do. Login never syncs RBAC. - Routes — Tenant API:
Route::middleware(['auth:tenant-api', 'tenant.user', 'verified', 'module:{slug}', 'can:{slug}.view'])->group(function () {
// …
});- Domain code — Flat under existing namespaces (no
Modules/package):
| Layer | Location |
|---|---|
| Models | app/Models/ + BelongsToTenant |
| Migrations | database/migrations/ |
| Factories | database/factories/ |
| Seeders | database/seeders/Tenant/ for local/demo only; production catalog/permission rows use data migrations |
| Controllers | app/Http/Controllers/Tenant/Api/V1/ |
| Form requests | app/Http/Requests/Tenant/Api/V1/{Module}/ |
| Resources | app/Http/Resources/Tenant/Api/V1/{Module}/ |
| Policies | app/Policies/ |
| Services | app/Services/Tenant/ |
| Events / Listeners | app/Events/, app/Listeners/ |
| Notifications | app/Notifications/Tenant/ |
- Frontend —
src/pages/{slug}/, API service, types,PERMISSIONS/QUERY_KEYS, nav item withpermissionandmodule, route underTenantProtectedRoute. - Tests — Pest feature suite + Playwright
test:e2e:{slug}. - Docs — User / developer / production guides, API, database, CHANGELOG.
Logging (both required)
| Layer | Mechanism | Purpose |
|---|---|---|
| Audit | PlatformAuditService → activity('platform') | Actor, workspace, IP, UA, action for create/update/delete/assign/status changes |
| Activity | Spatie LogsActivity on primary model | Attribute-level change history |
| Timeline | Domain *_activities table (when UX needs it) | User-facing history (notes, stage moves, assignments) |
Events
Dispatch domain events from the service layer. Listeners handle audit side-effects and notifications. Do not build per-module notification stacks outside Laravel notifications.
Dashboard widgets
When a module contributes dashboard cards:
- Extend
App\Services\Tenant\DashboardWidgetService(same registry pattern as Leads/Tasks). - Gate each widget on
EntitlementService::hasModule+ the user’s Spatie permission. - Apply assignee scoping with
ScopesToAssigneewhen the module uses{slug}.assign. - Return
{ id, module, permission, scope, data }objects only — the SPA renders byid. - Do not invent a parallel dashboard API; extend
DashboardWidgetService(Leads/Tasks/Calendar pattern).
In-app notifications
- Use Laravel notification channels required by the contract for that event (
databaseplusbroadcastfor realtime in-app delivery; addmailonly when product behavior requires it). - Implement
ShouldQueueand useApp\Notifications\Concerns\QueuesOnEmailsso jobs land on the dedicatedemailsqueue (php artisan queue:work --queue=emails). - Persist via the standard
notificationstable; expose tenant APIs under/notifications*(tenant-v1-notifications.md). - Register realtime in-app types in the SPA Notification Registry; Echo updates Query caches and polling remains a recovery path.
- Schedule due/overdue fan-out through
crm:send-due-notificationsrather than ad-hoc cron per module.
Settings
If the module needs settings, register keys in SystemSettingDefinitions / TenantSettingDefinitions and resolve Central → Tenant → system. Do not invent a parallel settings store.
Billing
Paid modules: catalog is_billable, marketplace install, ModuleSubscriptionService, consolidated billing. Never implement independent payment flows.
Frontend checklist
- Pages, forms, tables, filters, dialogs/drawers
- Shared design system (
PageHeader,DataTable,PermissionGate, empty/error/loading states) - Nav + breadcrumbs respect installed modules and user permissions
- Auth payload includes active module slugs for SPA gating
Testing checklist
Pest: unit where useful; feature CRUD; authorization; validation; tenant isolation; module middleware denial.
Playwright: dedicated suite; script test:e2e:{slug}; independently runnable.
Manual QA: Cursor browser — CRUD, search, filters, pagination, validation, authz, responsive layout, console, network.
Anti-patterns
- Laravel Modules / nwidart / plugin discovery
- Repositories layer
- Skipping
module:orcan:middleware - Static nav that ignores entitlements
- Custom audit/notification systems outside platform services
- Production
db:seed/CatalogSeederto register default modules - Login-time or dashboard-time permission “repair”
syncPermissions()on existing customized roles during deploy
Related
- Module Architecture
- Module Dependencies
- Module Licensing
- Production module registration
- Communication Templates — reusable platform module pattern
- Tenant provisioning