sepehr-mohseni / laramodutenant
A battle-tested modular multi-tenancy package for Laravel. Module scaffolding, tenant isolation, RBAC, unified API responses, and 45+ artisan commands.
Requires
- php: ^8.2
- illuminate/console: ^11.0|^12.0
- illuminate/contracts: ^11.0|^12.0
- illuminate/database: ^11.0|^12.0
- illuminate/http: ^11.0|^12.0
- illuminate/routing: ^11.0|^12.0
- illuminate/support: ^11.0|^12.0
- illuminate/validation: ^11.0|^12.0
Requires (Dev)
- laravel/pint: ^1.0
- orchestra/testbench: ^9.0|^10.0
- phpunit/phpunit: ^11.0
Suggests
- laravel/sanctum: Required for tenant-scoped API token authentication (^4.0)
- laravel/socialite: Required for OAuth social authentication support (^5.0|^6.0)
This package is auto-updated.
Last update: 2026-03-16 15:06:18 UTC
README
A senior-grade modular architecture + multi-tenancy package for Laravel 11+. Build scalable, tenant-aware applications with a powerful module system, RBAC, tenant-scoped authentication, and 45+ artisan commands — all in one package.
Table of Contents
- Features
- Requirements
- Installation
- Quick Start
- Architecture
- Multi-Tenancy
- RBAC (Role-Based Access Control)
- API Response Envelope
- Authentication
- Artisan Commands
- Configuration
- Customisation
- Facades
- Testing
- Changelog
- Contributing
- Acknowledgments
- License
Features
- Modular Architecture — Organize your application into self-contained modules, each with its own models, controllers, routes, migrations, seeders, configs, and translations.
- Single-Database Multi-Tenancy — Tenant isolation via a configurable column (
tenant_id) with automatic query scoping — no separate databases needed. - Combined Modules + Tenancy — The only package that natively integrates modular architecture with multi-tenancy in a single cohesive system.
- Per-Tenant Module Toggling — Enable or disable modules on a per-tenant basis, giving each tenant a tailored feature set.
- Built-in RBAC — Roles and permissions system with tenant-scoped assignment, middleware guards, and a clean trait-based API.
- Tenant-Scoped Authentication — Custom user provider that isolates authentication to the current tenant, preventing cross-tenant access.
- Tenant-Scoped Sanctum Tokens — API tokens are automatically prefixed with tenant context for full token isolation.
- Rate-Limited AuthManager — Ready-to-use login/logout with configurable rate limiting, tenant scoping, and token creation.
- Unified API Response Envelope — Consistent JSON response format (
success,message,data,errors,meta) across your entire API. - API Exception Renderer — Automatically converts exceptions into clean JSON responses for API routes.
- Base ApiController & ApiFormRequest — Extend these for built-in response helpers and consistent validation error formatting.
- Tenant Resolution Strategies — Resolve tenants from
X-Tenant-IDheader,X-Tenant-Slugheader, subdomain, or authenticated user's relation. - 45+ Artisan Commands — 28 module generators (
module:make-*), 8 tenant management commands, 8 module management commands, and an install command. - Full Module Scaffolder —
module:makegenerates a complete module with models, controllers, requests, resources, routes, migration, seeder, config, and lang files in one command. - Configurable Models — Swap the default Tenant, User, Role, and Permission models with your own implementations via config.
- Publishable Stubs — Customize the generated code by publishing and editing the package stubs.
- Facades — Clean
ModuleManagerandTenantContextfacades for expressive, readable code. - Multilingual Support — All response messages are translatable via Laravel's localization system.
- Zero Config to Start — Works out of the box with sensible defaults; customize only what you need.
- Laravel 11 & 12 Compatible — Built for modern Laravel with PHP 8.2+ features and auto-discovery.
Requirements
- PHP 8.2+
- Laravel 11.x or 12.x
Installation
composer require sepehr-mohseni/laramodutenant
The package auto-discovers its service provider. Then run the install command:
php artisan laramodutenant:install
This will:
- Publish the config file to
config/laramodutenant.php - Publish migrations for tenants, roles, and permissions tables
- Publish translation files
- Create the
modules/directory with a Core module
Then run migrations:
php artisan migrate
Quick Start
1. Add Traits to Your User Model
use Sepehr_Mohseni\LaraModuTenant\Traits\BelongsToTenant; use Sepehr_Mohseni\LaraModuTenant\Traits\HasRoles; class User extends Authenticatable { use BelongsToTenant; use HasRoles; // ... }
2. Create a Tenant
php artisan tenant:create "Acme Corp" --modules=core,crm
3. Create a Module
php artisan module:make Blog --models=Post,Category,Tag
This scaffolds a complete module with:
- Models with
BelongsToTenanttrait - API controllers extending
ApiController - Form requests extending
ApiFormRequest - API resources
- Routes with tenant & module middleware
- Migration, seeder, config, and lang files
4. Create Users & Roles
php artisan tenant:create-user acme-corp --name="John Doe" --email=john@acme.com php artisan tenant:create-role acme-corp admin --description="Administrator" php artisan tenant:assign-role acme-corp john@acme.com admin
Architecture
Directory Structure
your-app/
├── modules/
│ ├── Core/
│ │ ├── Config/
│ │ ├── Database/Migrations/
│ │ ├── Database/Seeders/
│ │ ├── Http/Controllers/
│ │ ├── Http/Requests/
│ │ ├── Http/Resources/
│ │ ├── Models/
│ │ ├── Providers/
│ │ ├── Lang/en/
│ │ ├── Routes/
│ │ ├── Services/
│ │ └── module.json
│ ├── CRM/
│ └── Blog/
├── config/
│ └── laramodutenant.php
Module JSON
Each module has a module.json that defines its metadata:
{
"name": "Blog",
"alias": "blog",
"description": "Blog module",
"order": 10,
"enabled": true,
"providers": [
"Modules\\Blog\\Providers\\BlogServiceProvider"
]
}
Multi-Tenancy
LaraModuTenant uses a single-database multi-tenancy approach with a configurable tenant column (default: tenant_id).
Tenant Resolution
The IdentifyTenant middleware resolves tenants from (in order):
X-Tenant-IDheaderX-Tenant-Slugheader- Subdomain
- Authenticated user's tenant relation
Tenant Context
use Sepehr_Mohseni\LaraModuTenant\Facades\TenantContext; // Check if a tenant is set TenantContext::check(); // Get the current tenant $tenant = TenantContext::get(); // Get tenant ID $id = TenantContext::id();
Automatic Scoping
Models using the BelongsToTenant trait are automatically scoped:
// Automatically filters by current tenant $posts = Post::all(); // WHERE tenant_id = {current_tenant_id} // Bypass tenant scoping $allPosts = Post::withoutTenancy()->get();
Per-Tenant Module Access
// Enable/disable modules per tenant $tenant->enableModule('blog'); $tenant->disableModule('blog'); $tenant->hasModule('blog'); // true/false
RBAC (Role-Based Access Control)
Roles & Permissions
// Check permissions $user->hasPermission('blog.posts.create'); $user->hasAnyPermission(['blog.posts.create', 'blog.posts.update']); $user->hasAllPermissions(['blog.posts.create', 'blog.posts.update']); // Manage roles $user->assignRole($role); $user->removeRole($role); $user->syncRoles([$adminRole, $editorRole]); $user->hasRole('admin');
Middleware
// In routes Route::middleware(['tenant', 'module:blog', 'permission:blog.posts.create']) ->group(function () { // ... });
API Response Envelope
All API responses follow a consistent envelope format:
use Sepehr_Mohseni\LaraModuTenant\Http\Responses\ApiResponse; // Success ApiResponse::success($data, 'Operation successful'); // Created ApiResponse::created($data); // Paginated ApiResponse::paginated($paginator, PostResource::class); // Error ApiResponse::error('Something went wrong', 500); // Validation error ApiResponse::validation($errors);
Response format:
{
"success": true,
"message": "Operation successful",
"data": { ... },
"meta": { ... }
}
Base Controller
Extend ApiController for convenient response helpers:
use Sepehr_Mohseni\LaraModuTenant\Http\Controllers\ApiController; class PostController extends ApiController { public function index() { return $this->paginated(Post::paginate(), PostResource::class); } public function store(StorePostRequest $request) { $post = Post::create($request->validated()); return $this->created(new PostResource($post)); } }
Authentication
Tenant-Scoped Auth
Use the TenantUserProvider to scope authentication to the current tenant:
// config/auth.php 'providers' => [ 'users' => [ 'driver' => 'tenant', // Use tenant-scoped provider 'model' => App\Models\User::class, ], ],
Tenant-Scoped Sanctum Tokens
Use HasTenantScopedTokens instead of HasApiTokens:
use Sepehr_Mohseni\LaraModuTenant\Auth\HasTenantScopedTokens; class User extends Authenticatable { use HasTenantScopedTokens; // Creates tokens with tenant context $token = $user->createToken('api-token'); // Revoke all tokens for the user's current tenant $user->revokeCurrentTenantTokens(); }
AuthManager
The AuthManager provides rate-limited, tenant-scoped login:
use Sepehr_Mohseni\LaraModuTenant\Auth\AuthManager; $auth = app(AuthManager::class); // Login (rate-limited, tenant-scoped) $result = $auth->attemptLogin([ 'email' => 'john@example.com', 'password' => 'secret', ]); // Returns: ['user' => $user, 'token' => 'plain-text-token'] // Logout current device $auth->logout($request); // Logout all devices $auth->logoutAll($request);
Artisan Commands
Module Commands
| Command | Description |
|---|---|
module:make {name} |
Scaffold a complete module |
module:list |
List all modules |
module:enable {name} |
Enable a module |
module:disable {name} |
Disable a module |
module:migrate {name} |
Run module migrations |
module:migrate-rollback {name} |
Rollback module migrations |
module:migrate-status {name} |
Show migration status |
module:seed {name} |
Run module seeders |
Module Generators (28 commands)
All Laravel make:* commands have module-aware equivalents:
php artisan module:make-model Blog Post php artisan module:make-controller Blog PostController php artisan module:make-migration Blog create_posts_table php artisan module:make-request Blog StorePostRequest php artisan module:make-resource Blog PostResource php artisan module:make-factory Blog PostFactory php artisan module:make-seeder Blog PostSeeder php artisan module:make-test Blog PostTest php artisan module:make-policy Blog PostPolicy php artisan module:make-event Blog PostCreated php artisan module:make-listener Blog SendPostNotification php artisan module:make-job Blog ProcessPost php artisan module:make-mail Blog PostPublished php artisan module:make-notification Blog PostCreatedNotification php artisan module:make-observer Blog PostObserver php artisan module:make-rule Blog ValidSlug php artisan module:make-cast Blog JsonCast php artisan module:make-scope Blog ActiveScope php artisan module:make-middleware Blog CheckPostAccess php artisan module:make-enum Blog PostStatus php artisan module:make-interface Blog PostRepositoryInterface php artisan module:make-trait Blog Sluggable php artisan module:make-class Blog PostService php artisan module:make-command Blog SyncPosts php artisan module:make-channel Blog PostChannel php artisan module:make-exception Blog PostNotFoundException php artisan module:make-provider Blog PostServiceProvider php artisan module:make-job-middleware Blog RateLimitedJob
Tenant Commands
| Command | Description |
|---|---|
tenant:create {name} |
Create a new tenant |
tenant:list |
List all tenants |
tenant:create-user {tenant} |
Create a user for a tenant |
tenant:create-role {tenant} {name} |
Create a role for a tenant |
tenant:assign-role {tenant} {email} {role} |
Assign a role to a user |
tenant:enable-module {tenant} {module} |
Enable a module for a tenant |
tenant:disable-module {tenant} {module} |
Disable a module for a tenant |
tenant:list-users {tenant} |
List users for a tenant |
Other Commands
| Command | Description |
|---|---|
laramodutenant:install |
Install the package |
Configuration
Publish and customise the config:
php artisan vendor:publish --tag=laramodutenant-config
Key configuration options:
return [ // Path to modules directory 'modules_path' => base_path('modules'), // Configurable models (swap with your own) 'tenant_model' => \Sepehr_Mohseni\LaraModuTenant\Models\Tenant::class, 'user_model' => \App\Models\User::class, 'role_model' => \Sepehr_Mohseni\LaraModuTenant\Models\Role::class, 'permission_model' => \Sepehr_Mohseni\LaraModuTenant\Models\Permission::class, // Tenant column name on user and module tables 'tenant_column' => 'tenant_id', // Tenant resolution (toggle individual resolvers) 'resolution' => [ 'header_id' => true, // X-Tenant-ID 'header_slug' => true, // X-Tenant-Slug 'subdomain' => true, // Match tenant domain against request host 'user_relation' => true, // Fall back to authenticated user's tenant ], // Middleware aliases 'middleware_aliases' => [ 'tenant' => \Sepehr_Mohseni\LaraModuTenant\Http\Middleware\IdentifyTenant::class, 'permission' => \Sepehr_Mohseni\LaraModuTenant\Http\Middleware\CheckPermission::class, 'module' => \Sepehr_Mohseni\LaraModuTenant\Http\Middleware\EnsureModuleEnabled::class, ], // API settings 'api' => [ 'exception_renderer' => true, 'route_prefix' => 'api/*', 'pagination' => ['per_page' => 15, 'max_per_page' => 100], ], // Auth settings 'auth' => [ 'tenant_user_provider' => true, 'scoped_tokens' => true, 'isolate_users' => true, 'max_login_attempts' => 5, 'token_expiration' => null, ], ];
Customisation
Custom Models
You can swap any model with your own implementation:
// config/laramodutenant.php 'tenant_model' => App\Models\Tenant::class, 'role_model' => App\Models\Role::class,
Your custom models should implement the relevant contracts:
use Sepehr_Mohseni\LaraModuTenant\Contracts\TenantModel; class Tenant extends Model implements TenantModel { // ... }
Custom Stubs
Publish stubs for customisation:
php artisan vendor:publish --tag=laramodutenant-stubs
Stubs are published to resources/stubs/vendor/laramodutenant/ and will be used by the module:make command.
Facades
use Sepehr_Mohseni\LaraModuTenant\Facades\ModuleManager; use Sepehr_Mohseni\LaraModuTenant\Facades\TenantContext; // Module management ModuleManager::all(); ModuleManager::enabled(); ModuleManager::find('Blog'); ModuleManager::has('Blog'); ModuleManager::enable('Blog'); ModuleManager::disable('Blog'); // Tenant context TenantContext::set($tenant); TenantContext::get(); TenantContext::id(); TenantContext::check(); TenantContext::forget();
Testing
composer test
Changelog
Please see CHANGELOG for recent changes.
Contributing
Contributions are welcome! Please see CONTRIBUTING for details.
Acknowledgments
LaraModuTenant is built on the shoulders of giants. Special thanks to these amazing packages and their maintainers for pioneering the patterns and ideas that inspired this work:
- nwidart/laravel-modules — The original Laravel modular architecture package that set the standard for module management. Much of our module scaffolding and
module.jsonmanifest approach was inspired by their excellent work. - stancl/tenancy — A comprehensive multi-tenancy solution for Laravel. Their approach to tenant resolution, automatic scoping, and tenant-aware events served as a fantastic reference.
- spatie/laravel-permission — The gold standard for roles and permissions in Laravel. Our RBAC system draws heavy inspiration from their clean, trait-based API design.
- spatie/laravel-multitenancy — A beautifully opinionated multi-tenancy package by Spatie. Their single-database tenancy patterns influenced our tenant scoping implementation.
- archtechx/tenancy — An innovative approach to Laravel tenancy focusing on developer experience and flexibility.
- Laravel — The framework that makes all of this possible. Thank you Taylor Otwell and the entire Laravel community.
We are grateful to the open-source community for sharing their knowledge, code, and creativity. If you find LaraModuTenant useful, please consider giving these packages a star as well. ⭐
License
The MIT License (MIT). Please see License File for more information.