Module System
Understanding LaraDashboard's modular architecture powered by Laravel Modules. Learn how modules work, are loaded, and interact with the core system.
Module System
LaraDashboard uses a powerful module system powered by nwidart/laravel-modules. Modules are self-contained packages that extend LaraDashboard's functionality without modifying core code.
Overview
What Are Modules?
Modules are independent packages that:
- Have their own MVC structure
- Include migrations, seeders, and config
- Register routes, views, and assets
- Can be installed, activated, and removed
- Are version-controlled independently
Benefits of Modular Architecture
| Benefit | Description |
|---|---|
| Separation of Concerns | Each feature lives in its own module |
| Independent Updates | Update modules without touching core |
| Easy Distribution | Share modules as ZIP files or via marketplace |
| Clean Codebase | Core remains lightweight and focused |
| Team Collaboration | Different teams can work on different modules |
Module Lifecycle
┌──────────────────────────────────────────────────────────────────┐
│ Module Lifecycle │
├──────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Uploaded │────▶│Installed │────▶│ Activated│ │
│ └──────────┘ └──────────┘ └────┬─────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ Running in App │ │
│ │ • Service providers booted │ │
│ │ • Routes registered │ │
│ │ • Views available │ │
│ │ • Migrations applied │ │
│ └─────────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────┼─────────────────────┐│
│ ▼ ▼ ▼│
│ ┌──────────┐ ┌───────────┐ ┌───────┐│
│ │Deactivated│ │ Updated │ │Deleted││
│ └──────────┘ └───────────┘ └───────┘│
│ │
└──────────────────────────────────────────────────────────────────┘
Module Structure
Every module follows a standard structure:
modules/YourModule/
├── app/
│ ├── Http/
│ │ ├── Controllers/
│ │ │ └── YourModuleController.php
│ │ ├── Middleware/
│ │ └── Requests/
│ ├── Livewire/
│ │ └── Admin/
│ │ └── Dashboard.php
│ ├── Models/
│ │ └── YourModel.php
│ ├── Providers/
│ │ ├── YourModuleServiceProvider.php
│ │ ├── RouteServiceProvider.php
│ │ └── EventServiceProvider.php
│ └── Services/
│ └── YourModuleService.php
│
├── config/
│ └── config.php
│
├── database/
│ ├── migrations/
│ │ └── 2024_01_01_000000_create_your_table.php
│ ├── seeders/
│ │ └── YourModuleDatabaseSeeder.php
│ └── factories/
│ └── YourModelFactory.php
│
├── resources/
│ ├── assets/
│ │ ├── css/
│ │ └── js/
│ └── views/
│ ├── admin/
│ │ └── index.blade.php
│ └── livewire/
│ └── admin/
│ └── dashboard.blade.php
│
├── routes/
│ ├── web.php
│ └── api.php
│
├── tests/
│ ├── Feature/
│ └── Unit/
│
├── composer.json
├── module.json
├── package.json (optional)
└── README.md
Configuration Files
module.json
The module manifest file:
{
"name": "YourModule",
"alias": "yourmodule",
"description": "Description of your module",
"keywords": ["feature", "extension"],
"priority": 0,
"providers": [
"Modules\\YourModule\\Providers\\YourModuleServiceProvider"
],
"files": [],
"requires": []
}
| Field | Description |
|---|---|
name |
Module display name |
alias |
Lowercase identifier |
description |
Short description |
keywords |
Searchable keywords |
priority |
Load order (higher = earlier) |
providers |
Service providers to register |
files |
Files to autoload |
requires |
Module dependencies |
composer.json
Module-specific dependencies:
{
"name": "laradashboard/yourmodule",
"description": "Your module description",
"type": "laradashboard-module",
"require": {
"php": "^8.3"
},
"autoload": {
"psr-4": {
"Modules\\YourModule\\": ""
}
},
"extra": {
"laravel": {
"providers": [
"Modules\\YourModule\\Providers\\YourModuleServiceProvider"
]
}
}
}
config/config.php
Module configuration:
<?php
return [
'name' => 'YourModule',
// Module-specific settings
'settings' => [
'feature_enabled' => env('YOURMODULE_FEATURE_ENABLED', true),
'max_items' => env('YOURMODULE_MAX_ITEMS', 100),
],
// Route configuration
'routes' => [
'prefix' => 'yourmodule',
'middleware' => ['web', 'auth'],
],
];
Service Providers
Main Service Provider
<?php
namespace Modules\YourModule\Providers;
use Illuminate\Support\ServiceProvider;
class YourModuleServiceProvider extends ServiceProvider
{
protected string $moduleName = 'YourModule';
protected string $moduleNameLower = 'yourmodule';
public function boot(): void
{
$this->registerTranslations();
$this->registerConfig();
$this->registerViews();
$this->loadMigrationsFrom(
module_path($this->moduleName, 'database/migrations')
);
}
public function register(): void
{
$this->app->register(RouteServiceProvider::class);
$this->app->register(EventServiceProvider::class);
// Register module services
$this->app->singleton(YourModuleService::class, function ($app) {
return new YourModuleService();
});
}
protected function registerConfig(): void
{
$this->publishes([
module_path($this->moduleName, 'config/config.php')
=> config_path($this->moduleNameLower . '.php'),
], 'config');
$this->mergeConfigFrom(
module_path($this->moduleName, 'config/config.php'),
$this->moduleNameLower
);
}
protected function registerViews(): void
{
$viewPath = resource_path('views/modules/' . $this->moduleNameLower);
$sourcePath = module_path($this->moduleName, 'resources/views');
$this->publishes([
$sourcePath => $viewPath
], ['views', $this->moduleNameLower . '-module-views']);
$this->loadViewsFrom(array_merge($this->getPublishableViewPaths(), [
$sourcePath
]), $this->moduleNameLower);
}
protected function registerTranslations(): void
{
$langPath = resource_path('lang/modules/' . $this->moduleNameLower);
if (is_dir($langPath)) {
$this->loadTranslationsFrom($langPath, $this->moduleNameLower);
} else {
$this->loadTranslationsFrom(
module_path($this->moduleName, 'resources/lang'),
$this->moduleNameLower
);
}
}
private function getPublishableViewPaths(): array
{
$paths = [];
foreach (config('view.paths') as $path) {
if (is_dir($path . '/modules/' . $this->moduleNameLower)) {
$paths[] = $path . '/modules/' . $this->moduleNameLower;
}
}
return $paths;
}
}
Route Service Provider
<?php
namespace Modules\YourModule\Providers;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Route;
class RouteServiceProvider extends ServiceProvider
{
protected string $moduleNamespace = 'Modules\YourModule\App\Http\Controllers';
public function boot(): void
{
parent::boot();
}
public function map(): void
{
$this->mapApiRoutes();
$this->mapWebRoutes();
}
protected function mapWebRoutes(): void
{
Route::middleware('web')
->namespace($this->moduleNamespace)
->group(module_path('YourModule', '/routes/web.php'));
}
protected function mapApiRoutes(): void
{
Route::prefix('api')
->middleware('api')
->namespace($this->moduleNamespace)
->group(module_path('YourModule', '/routes/api.php'));
}
}
Module Loading
Automatic Discovery
LaraDashboard automatically discovers modules in the modules/ directory:
// config/modules.php
return [
'paths' => [
base_path('Modules'),
base_path('modules'),
],
'auto_discover' => true,
];
Load Order
Modules are loaded based on:
- Priority - Higher priority loads first
- Dependencies - Required modules load before dependents
- Alphabetical - When priority is equal
// module.json
{
"priority": 100, // Load early
"requires": ["AnotherModule"] // Load after AnotherModule
}
Module vendor/ Autoloaders
A module may ship its own Composer dependencies in modules/YourModule/vendor/. Before Laravel boots, bootstrap/modules.php includes each enabled module's vendor/autoload.php after the core autoloader (v1.5.0+). Core classes therefore always win: if a module bundles its own copy of illuminate/*, it can't shadow the framework the app runs on. Because the module autoloader is already loaded, a later require_once of it from your service provider does nothing (v1.5.1).
Still, keep illuminate/* and laravel/* out of your module's require section — list only packages core doesn't already provide. Mixing two framework versions used to break migrations with errors like Relation::withConstraintsForNestedRelation does not exist.
bootstrap/modules.php also checks each module before boot and disables a broken one instead of letting it take the whole site down.
Conditional Loading
Load modules conditionally:
// In YourModuleServiceProvider
public function register(): void
{
// Only load if feature flag is enabled
if (!config('yourmodule.enabled', true)) {
return;
}
// Continue registration...
}
Database Management
Migrations
Create migrations for your module:
php artisan module:make-migration create_your_table YourModule
Migration file:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('yourmodule_items', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->string('name');
$table->text('description')->nullable();
$table->boolean('is_active')->default(true);
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('yourmodule_items');
}
};
Run module migrations:
# All modules
php artisan module:migrate
# Specific module
php artisan module:migrate YourModule
# Rollback
php artisan module:migrate-rollback YourModule
Seeders
<?php
namespace Modules\YourModule\Database\Seeders;
use Illuminate\Database\Seeder;
class YourModuleDatabaseSeeder extends Seeder
{
public function run(): void
{
// Seed permissions
$this->call(PermissionsSeeder::class);
// Seed default data
$this->call(DefaultDataSeeder::class);
}
}
Run seeders:
php artisan module:seed YourModule
Module Communication
Using Hooks
Modules communicate via the hooks system:
// Module A: Register a hook
add_action('post.created', function (Post $post) {
// Handle post creation
YourModuleService::handlePostCreated($post);
});
// Module A: Register a filter
add_filter('post.content', function (string $content, Post $post) {
// Modify content
return $this->processContent($content);
}, 10, 2);
Using Events
// Module A: Dispatch event
event(new YourModuleEvent($data));
// Module B: Listen for event
// In EventServiceProvider
protected $listen = [
\Modules\ModuleA\Events\YourModuleEvent::class => [
\Modules\ModuleB\Listeners\HandleYourModuleEvent::class,
],
];
Using Service Container
// Module A: Bind service
$this->app->bind(YourContractInterface::class, YourImplementation::class);
// Module B: Resolve service
$service = app(YourContractInterface::class);
Module Management
Via Admin Panel
- Navigate to More → Modules
- Upload module ZIP file or browse marketplace
- Click Install
- Activate the module
- Run migrations if prompted
Installing, updating, replacing and activating modules is limited to Superadmins, because a module runs code on your server. Replacing an installed module with an uploaded ZIP activates the new version automatically.
The install folder name comes from the module's own metadata (module.json providers, composer.json PSR-4 keys or the module name). Since v1.5.0 it must be a plain namespace segment such as Crm — values like ../public/x are rejected, so a crafted ZIP can't write files outside modules/ (GHSA-75q6-qrv2-96rx).
Via Artisan Commands
# List all modules
php artisan module:list
# Enable module
php artisan module:enable YourModule
# Disable module
php artisan module:disable YourModule
# Create new module
php artisan module:make YourModule
# Generate module components
php artisan module:make-controller YourController YourModule
php artisan module:make-model YourModel YourModule
php artisan module:make-migration create_table YourModule
php artisan module:make-seeder YourSeeder YourModule
php artisan module:make-request YourRequest YourModule
php artisan module:make-policy YourPolicy YourModule
Module API
ModuleService
use App\Services\Modules\ModuleService;
// Get module service
$moduleService = app(ModuleService::class);
// Check if module is enabled
$isEnabled = $moduleService->isEnabled('YourModule');
// Get module info
$module = $moduleService->find('YourModule');
// Get all enabled modules
$enabled = $moduleService->getEnabled();
// Install module from path
$moduleService->install('/path/to/module.zip');
// Enable/disable
$moduleService->enable('YourModule');
$moduleService->disable('YourModule');
// Delete module
$moduleService->delete('YourModule');
Module Helpers
// Get module path
$path = module_path('YourModule', 'resources/views');
// Get module asset URL
$url = module_asset('YourModule', 'css/style.css');
// Check module status
if (Module::isEnabled('YourModule')) {
// Module is active
}
// Get module config
$value = config('yourmodule.setting_key');
Best Practices
Naming Conventions
| Type | Convention | Example |
|---|---|---|
| Module name | PascalCase | YourModule |
| Alias | lowercase | yourmodule |
| Tables | prefix_table | yourmodule_items |
| Config | lowercase | yourmodule.php |
| Views namespace | lowercase | yourmodule::view |
Dependency Management
// module.json
{
"requires": [
"CoreModule",
"AnotherModule"
]
}
Check dependencies in service provider:
public function boot(): void
{
if (!Module::isEnabled('RequiredModule')) {
throw new ModuleDependencyException(
'YourModule requires RequiredModule to be enabled.'
);
}
}
Permissions
Register module-specific permissions:
// In migration or seeder
Permission::create(['name' => 'yourmodule.view', 'group' => 'yourmodule']);
Permission::create(['name' => 'yourmodule.create', 'group' => 'yourmodule']);
Permission::create(['name' => 'yourmodule.edit', 'group' => 'yourmodule']);
Permission::create(['name' => 'yourmodule.delete', 'group' => 'yourmodule']);
Configuration
Allow users to configure modules:
// Publish config
php artisan vendor:publish --tag=yourmodule-config
// Access config
$value = config('yourmodule.feature_enabled');
Troubleshooting
Module Not Loading
- Check
module.jsonsyntax - Verify service provider namespace
- Clear caches:
php artisan module:clear-compiled php artisan config:clear php artisan cache:clear
Migration Issues
# Reset and re-run migrations
php artisan module:migrate-refresh YourModule
# Check migration status
php artisan module:migrate-status YourModule
View Not Found
# Clear view cache
php artisan view:clear
# Publish module views
php artisan vendor:publish --tag=yourmodule-module-views
Next Steps
- Module Development Guide - Build your own module
- Hooks System - Extend functionality
- Architecture Overview - System design