Standard Module Layout
Laravel Modular follows Laravel’s conventions with a standard directory structure:
app-modules/
└── user-management/
├── composer.json
├── database/
│ ├── factories/
│ ├── migrations/
│ └── seeders/
├── resources/
│ ├── lang/
│ └── views/
├── routes/
│ ├── api.php
│ └── web.php
├── src/
│ ├── Console/
│ │ └── Commands/
│ ├── Events/
│ ├── Listeners/
│ ├── Models/
│ ├── Policies/
│ ├── Providers/
│ │ └── UserManagementServiceProvider.php
│ └── View/
│ └── Components/
└── tests/
This structure mirrors a standard Laravel application, making it familiar to Laravel developers.
Required Files
composer.json
Every module must have a composer.json file in its root:
{
"name" : "modules/user-management" ,
"description" : "User management module" ,
"type" : "library" ,
"version" : "1.0" ,
"license" : "proprietary" ,
"require" : {},
"autoload" : {
"psr-4" : {
"Modules \\ UserManagement \\ " : "src/" ,
"Modules \\ UserManagement \\ Tests \\ " : "tests/" ,
"Modules \\ UserManagement \\ Database \\ Factories \\ " : "database/factories/" ,
"Modules \\ UserManagement \\ Database \\ Seeders \\ " : "database/seeders/"
}
},
"minimum-stability" : "stable" ,
"extra" : {
"laravel" : {
"providers" : [
"Modules \\ UserManagement \\ Providers \\ UserManagementServiceProvider"
]
}
}
}
The autoload.psr-4 section defines namespace-to-directory mappings:
Modules\\UserManagement\\ → src/ (main source code)
Modules\\UserManagement\\Tests\\ → tests/ (tests)
Modules\\UserManagement\\Database\\Factories\\ → database/factories/ (factories)
Modules\\UserManagement\\Database\\Seeders\\ → database/seeders/ (seeders)
The extra.laravel.providers section allows Laravel to auto-discover service providers: "extra" : {
"laravel" : {
"providers" : [
"Modules \\ UserManagement \\ Providers \\ UserManagementServiceProvider"
]
}
}
This is optional but recommended for modules that need custom service providers.
Directory Conventions
Source Directory (src/)
The src/ directory contains all PHP source code:
Console/Commands/ Artisan commands
Listeners/ Event listeners
Policies/ Authorization policies
Providers/ Service providers
View/Components/ Blade components
Http/Controllers/ HTTP controllers
Database Directory
database/
├── factories/ # Model factories
├── migrations/ # Database migrations
└── seeders/ # Database seeders
Migrations are automatically discovered and can be run with php artisan migrate.
Resources Directory
resources/
├── lang/ # Translation files
│ ├── en.json
│ └── en/
│ └── messages.php
└── views/ # Blade templates
├── index.blade.php
└── components/
└── alert.blade.php
Views are automatically registered with a namespace matching the module name:
// Access module views using the module name as namespace
return view ( 'user-management::index' );
// Access component views
return view ( 'user-management::components.alert' );
Routes Directory
routes/
├── api.php # API routes
├── web.php # Web routes
└── channels.php # Broadcasting channels
Route files are loaded alphabetically. You can name them anything:
routes/
├── 01-api.php
├── 02-web.php
└── 03-admin.php
Route files are included in alphabetical order. Use numeric prefixes if order matters.
Tests Directory
tests/
├── Feature/
│ └── UserTest.php
└── Unit/
└── UserModelTest.php
Namespace Conventions
Modules use PSR-4 namespacing based on the configuration:
Default Namespace
By default, modules use the Modules namespace:
// config/app-modules.php
'modules_namespace' => 'Modules' ,
For a module named user-management, classes would be:
Modules \ UserManagement \ Models \ User
Modules \ UserManagement \ Console \ Commands \ CreateUserCommand
Modules \ UserManagement \ Providers \ UserManagementServiceProvider
Custom Namespace
You can configure a custom namespace:
// config/app-modules.php
'modules_namespace' => 'MyCompany' ,
Classes would then be:
MyCompany \ UserManagement \ Models \ User
MyCompany \ UserManagement \ Console \ Commands \ CreateUserCommand
Using your company/organization name makes it easier to extract modules to standalone packages later.
File Naming Conventions
Models
// src/Models/User.php
namespace Modules\UserManagement\Models ;
use Illuminate\Database\Eloquent\ Model ;
class User extends Model
{
//
}
Controllers
// src/Http/Controllers/UserController.php
namespace Modules\UserManagement\Http\Controllers ;
use App\Http\Controllers\ Controller ;
class UserController extends Controller
{
//
}
Commands
// src/Console/Commands/CreateUserCommand.php
namespace Modules\UserManagement\Console\Commands ;
use Illuminate\Console\ Command ;
class CreateUserCommand extends Command
{
protected $signature = 'user:create' ;
//
}
Policies
// src/Policies/UserPolicy.php
namespace Modules\UserManagement\Policies ;
use Modules\UserManagement\Models\ User ;
class UserPolicy
{
public function update ( User $user , User $model )
{
return $user -> id === $model -> id ;
}
}
Policies are automatically discovered and mapped to models by the GatePlugin.
Service Provider Structure
Modules can have optional service providers:
// src/Providers/UserManagementServiceProvider.php
namespace Modules\UserManagement\Providers ;
use Illuminate\Support\ ServiceProvider ;
class UserManagementServiceProvider extends ServiceProvider
{
public function register ()
{
// Register bindings
$this -> app -> singleton ( UserRepository :: class );
}
public function boot ()
{
// Boot logic (if needed)
$this -> loadMigrationsFrom ( __DIR__ . '/../../database/migrations' );
}
}
Most modules don’t need custom service providers. Only create one if you need to register services or run boot logic.
Example Module: test-module
Here’s the structure from Laravel Modular’s test suite:
// composer.json
{
"name" : "modules/test-module" ,
"description" : "" ,
"type" : "library" ,
"version" : "1.0" ,
"license" : "proprietary" ,
"autoload" : {
"psr-4" : {
"Modules \\ TestModule \\ " : "src/" ,
"Modules \\ TestModule \\ Tests \\ " : "tests/" ,
"Modules \\ TestModule \\ Database \\ Factories \\ " : "database/factories/" ,
"Modules \\ TestModule \\ Database \\ Seeders \\ " : "database/seeders/"
}
}
}
// src/Models/TestModel.php
namespace Modules\TestModule\Models ;
use Illuminate\Database\Eloquent\ Model ;
class TestModel extends Model
{
protected $fillable = [ 'name' , 'email' ];
}
// src/Policies/TestModelPolicy.php
namespace Modules\TestModule\Policies ;
class TestModelPolicy
{
public function view ( $user , $model )
{
return true ;
}
}
// src/Console/Commands/TestCommand.php
namespace Modules\TestModule\Console\Commands ;
use Illuminate\Console\ Command ;
class TestCommand extends Command
{
protected $signature = 'test:command' ;
protected $description = 'Test command' ;
public function handle ()
{
$this -> info ( 'Test command executed!' );
}
}
// src/View/Components/Alert.php
namespace Modules\TestModule\View\Components ;
use Illuminate\View\ Component ;
class Alert extends Component
{
public function __construct (
public string $type = 'info'
) {}
public function render ()
{
return view ( 'test-module::components.alert' );
}
}
<!-- resources/views/components/alert.blade.php -->
< div class = "alert alert- {{ $type }} " >
{{ $slot }}
</ div >
// routes/test-module-routes.php
use Illuminate\Support\Facades\ Route ;
Route :: get ( '/test' , function () {
return view ( 'test-module::index' );
});
Discovery Paths
Laravel Modular uses the FinderFactory to discover files in specific locations:
Location: */src/Console/Commands/*.phpFinder: public function commandFileFinder () : FinderCollection
{
return FinderCollection :: forFiles ()
-> name ( '*.php' )
-> inOrEmpty ( $this -> base_path . '/*/src/Console/Commands' );
}
Location: */routes/*.php (depth 0, sorted by name)Finder: public function routeFileFinder () : FinderCollection
{
return FinderCollection :: forFiles ()
-> depth ( 0 )
-> name ( '*.php' )
-> sortByName ()
-> inOrEmpty ( $this -> base_path . '/*/routes' );
}
Location: */resources/views (directories)Finder: public function viewDirectoryFinder () : FinderCollection
{
return FinderCollection :: forDirectories ()
-> depth ( 0 )
-> name ( 'views' )
-> inOrEmpty ( $this -> base_path . '/*/resources/' );
}
Location: */database/migrations (directories)Finder: public function migrationDirectoryFinder () : FinderCollection
{
return FinderCollection :: forDirectories ()
-> depth ( 0 )
-> name ( 'migrations' )
-> inOrEmpty ( $this -> base_path . '/*/database/' );
}
Location: */src/Models/*.phpFinder: public function modelFileFinder () : FinderCollection
{
return FinderCollection :: forFiles ()
-> name ( '*.php' )
-> inOrEmpty ( $this -> base_path . '/*/src/Models' );
}
Location: */src/View/Components/*.phpFinder: public function bladeComponentFileFinder () : FinderCollection
{
return FinderCollection :: forFiles ()
-> name ( '*.php' )
-> inOrEmpty ( $this -> base_path . '/*/src/View/Components' );
}
Best Practices
Keep Modules Focused
Each module should have a single, clear purpose:
user-management - User CRUD and authentication
billing - Payment processing and invoicing
notifications - Email and SMS notifications
Use Consistent Naming
Module directory: kebab-case (e.g., user-management)
Namespace: PascalCase (e.g., UserManagement)
Classes: PascalCase (e.g., UserController)
Files: Match class name (e.g., UserController.php)
Leverage Laravel Conventions
Models in src/Models
Controllers in src/Http/Controllers
Commands in src/Console/Commands
Policies in src/Policies
Following Laravel’s conventions ensures automatic discovery and a familiar structure for all developers.
Minimal Service Providers
Only create service providers when you need to:
Register singleton bindings
Load additional migrations
Publish assets or config files
Run boot-time logic
Most modules don’t need custom service providers.
Document Module Dependencies
If your module depends on other modules, document it:
// composer.json
{
"require" : {
"modules/user-management" : "^1.0"
}
}
Inter-module dependencies can make your application harder to maintain. Consider if the functionality belongs in a shared module instead.