A battle-tested modular multi-tenancy package for Laravel. Module scaffolding, tenant isolation, RBAC, unified API responses, and 45+ artisan commands.
sepehr-mohseni/laramodutenant is a Laravel package for a battle-tested modular multi-tenancy package for laravel. module scaffolding, tenant isolation, rbac, unified api responses, and 45+ artisan commands..
It currently has 3 GitHub stars and 4 downloads on Packagist (latest version 1.0.0).
Install it with composer require sepehr-mohseni/laramodutenant.
Discover more Laravel packages by sepehr-mohseni
or browse all Laravel packages to compare alternatives.
Last updated
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.
tenant_id) with automatic query scoping — no separate databases needed.success, message, data, errors, meta) across your entire API.X-Tenant-ID header, X-Tenant-Slug header, subdomain, or authenticated user's relation.module:make-*), 8 tenant management commands, 8 module management commands, and an install command.module:make generates a complete module with models, controllers, requests, resources, routes, migration, seeder, config, and lang files in one command.ModuleManager and TenantContext facades for expressive, readable code.composer require sepehr-mohseni/laramodutenant
The package auto-discovers its service provider. Then run the install command:
php artisan laramodutenant:install
This will:
config/laramodutenant.phpmodules/ directory with a Core moduleThen run migrations:
php artisan migrate
use Sepehr_Mohseni\LaraModuTenant\Traits\BelongsToTenant;
use Sepehr_Mohseni\LaraModuTenant\Traits\HasRoles;
class User extends Authenticatable
{
use BelongsToTenant;
use HasRoles;
// ...
}
php artisan tenant:create "Acme Corp" --modules=core,crm
php artisan module:make Blog --models=Post,Category,Tag
This scaffolds a complete module with:
BelongsToTenant traitApiControllerApiFormRequestphp artisan tenant:create-user acme-corp --name="John Doe" [email protected]
php artisan tenant:create-role acme-corp admin --description="Administrator"
php artisan tenant:assign-role acme-corp [email protected] admin
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
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"
]
}
LaraModuTenant uses a single-database multi-tenancy approach with a configurable tenant column (default: tenant_id).
The IdentifyTenant middleware resolves tenants from (in order):
X-Tenant-ID headerX-Tenant-Slug headeruse 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();
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();
// Enable/disable modules per tenant
$tenant->enableModule('blog');
$tenant->disableModule('blog');
$tenant->hasModule('blog'); // true/false
// 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');
// In routes
Route::middleware(['tenant', 'module:blog', 'permission:blog.posts.create'])
->group(function () {
// ...
});
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": { ... }
}
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));
}
}
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,
],
],
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();
}
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' => '[email protected]',
'password' => 'secret',
]);
// Returns: ['user' => $user, 'token' => 'plain-text-token']
// Logout current device
$auth->logout($request);
// Logout all devices
$auth->logoutAll($request);
| 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 |
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
| 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 |
| Command | Description |
|---------|-------------|
| laramodutenant:install | Install the package |
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,
],
];
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
{
// ...
}
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.
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();
composer test
Please see CHANGELOG for recent changes.
Contributions are welcome! Please see CONTRIBUTING for details.
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:
module.json manifest approach was inspired by their excellent work.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. ⭐
The MIT License (MIT). Please see License File for more information.