sepehr-mohseni/laramodutenant

A battle-tested modular multi-tenancy package for Laravel. Module scaffolding, tenant isolation, RBAC, unified API responses, and 45+ artisan commands.

Maintainers

Package info

github.com/sepehr-mohseni/laramodutenant

pkg:composer/sepehr-mohseni/laramodutenant

Statistics

Installs: 4

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

1.0.0 2026-03-16 15:04 UTC

This package is auto-updated.

Last update: 2026-03-16 15:06:18 UTC


README

Latest Version on Packagist License

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

  • 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-ID header, X-Tenant-Slug header, 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 Scaffoldermodule:make generates 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 ModuleManager and TenantContext facades 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:

  1. Publish the config file to config/laramodutenant.php
  2. Publish migrations for tenants, roles, and permissions tables
  3. Publish translation files
  4. 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 BelongsToTenant trait
  • 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):

  1. X-Tenant-ID header
  2. X-Tenant-Slug header
  3. Subdomain
  4. 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.json manifest 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.