一键导入
laravel-multi-tenancy
Multi-tenant application architecture patterns. Use when working with tenant isolation, tenant scoping, or multi-tenant systems.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Multi-tenant application architecture patterns. Use when working with tenant isolation, tenant scoping, or multi-tenant systems.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Form request validation and comprehensive validation testing. Use when creating or modifying form requests, validation rules, or validation tests.
Feature module pattern organizing domain logic into queries, mutations, and actions. Use when implementing data fetching with filters, API mutations with loading states, business logic with UI feedback, or organizing domain-specific code.
File-based routing with page patterns for lists, details, and navigation. Use when creating pages, defining page meta (permissions, layouts), implementing list/detail patterns, or setting up breadcrumbs and headers.
Foundational architecture for Nuxt 4 + Vue 3 + Nuxt UI applications. Use when starting new projects, understanding project structure, or making architectural decisions about directory organization, technology choices, and pattern selection.
Vue component patterns with Composition API and script setup. Use when creating components, understanding script setup order convention, organizing component directories, or implementing component patterns like slideovers, modals, and tables.
Creating custom Vue composables with proper patterns. Use when building reusable stateful logic, shared state management, or encapsulating feature-specific behavior.
| name | laravel-multi-tenancy |
| description | Multi-tenant application architecture patterns. Use when working with tenant isolation, tenant scoping, or multi-tenant systems. |
Multi-tenancy separates application logic into central (non-tenant) and tenanted (tenant-specific) contexts.
Related guides:
Multi-tenancy provides:
Use multi-tenancy when:
Don't use when:
app/
├── Actions/
│ ├── Central/ # Non-tenant actions
│ │ ├── Tenant/
│ │ │ ├── CreateTenantAction.php
│ │ │ └── DeleteTenantAction.php
│ │ └── User/
│ │ └── CreateCentralUserAction.php
│ └── Tenanted/ # Tenant-specific actions
│ ├── Order/
│ │ └── CreateOrderAction.php
│ └── Customer/
│ └── CreateCustomerAction.php
├── Data/
│ ├── Central/ # Central DTOs
│ └── Tenanted/ # Tenant DTOs
├── Http/
│ ├── Central/ # Central routes (tenant management)
│ ├── Web/ # Tenant application routes
│ └── Api/ # Public API (tenant-scoped)
├── Models/ # All models in standard location
│ ├── Tenant.php # Central model
│ ├── Order.php # Tenanted model
│ └── Customer.php
└── Services/
└── Tenancy/
├── Landlord.php
└── Facades/
└── Landlord.php
Central actions manage tenants and cross-tenant operations.
<?php
declare(strict_types=1);
namespace App\Actions\Central\Tenant;
use App\Data\Central\CreateTenantData;
use App\Models\Tenant;
use Illuminate\Support\Facades\DB;
class CreateTenantAction
{
public function __construct(
private readonly CreateTenantDatabaseAction $createDatabase,
) {}
public function __invoke(CreateTenantData $data): Tenant
{
return DB::transaction(function () use ($data): Tenant {
$this->guard($data);
$tenant = $this->createTenant($data);
($this->createDatabase)($tenant);
return $tenant;
});
}
private function guard(CreateTenantData $data): void
{
throw_if(
Tenant::where('domain', $data->domain)->exists(),
TenantDomainAlreadyExistsException::forDomain($data->domain)
);
}
private function createTenant(CreateTenantData $data): Tenant
{
return Tenant::create([
'id' => $data->tenantId,
'name' => $data->name,
'domain' => $data->domain,
]);
}
}
Tenanted actions operate within a specific tenant's context. All queries automatically scoped.
<?php
declare(strict_types=1);
namespace App\Actions\Tenanted\Order;
use App\Data\Tenanted\CreateOrderData;
use App\Models\Order;
use App\Models\User;
use Illuminate\Support\Facades\DB;
class CreateOrderAction
{
public function __invoke(User $user, CreateOrderData $data): Order
{
return DB::transaction(function () use ($user, $data): Order {
// Automatically scoped to current tenant
$order = $user->orders()->create([
'status' => $data->status,
'total' => $data->total,
]);
$this->createOrderItems($order, $data->items);
return $order;
});
}
private function createOrderItems(Order $order, array $items): void
{
foreach ($items as $item) {
$order->items()->create([
'product_id' => $item->productId,
'quantity' => $item->quantity,
'price' => $item->price,
]);
}
}
}
Wrap Stancl Tenancy in a Landlord service class for a cleaner API:
<?php
declare(strict_types=1);
namespace App\Services\Tenancy;
use App\Models\Tenant;
class Landlord
{
public static function tenant(): ?Tenant
{
return tenant();
}
public static function initialize(Tenant|int|string $tenant): void
{
tenancy()->initialize($tenant);
}
public static function end(): void
{
tenancy()->end();
}
public static function runAsCentral(callable $callback): mixed
{
return tenancy()->central($callback);
}
public function tenantId(): ?string
{
return tenant()?->getTenantKey();
}
public function eachTenant(callable $callback): void
{
Tenant::each(function (Tenant $tenant) use ($callback): void {
$this->runAs($tenant, $callback);
});
}
public function runAs(Tenant|int|string $tenant, callable $callback): mixed
{
if (! $tenant instanceof Tenant) {
$tenant = tenancy()->find($tenant);
}
return tenancy()->run($tenant, $callback);
}
}
Usage:
use App\Services\Tenancy\Landlord;
$tenant = Landlord::tenant();
$tenantId = Landlord::tenantId();
if (Landlord::tenant() !== null) {
// Tenant-specific logic
}
Landlord::runAs($tenant, function () {
Order::create([...]);
});
Landlord::runAsCentral(function () {
Tenant::create([...]);
});
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
class IdentifyTenant extends InitializeTenancyByDomain
{
// Tenant identified by domain (e.g., tenant1.myapp.com)
}
use Stancl\Tenancy\Middleware\InitializeTenancyBySubdomain;
class IdentifyTenant extends InitializeTenancyBySubdomain
{
// Tenant identified by subdomain
}
use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData;
class IdentifyTenant extends InitializeTenancyByRequestData
{
public static string $header = 'X-Tenant';
}
// routes/tenant.php
Route::middleware(['tenant'])->group(function () {
Route::get('/orders', [OrderController::class, 'index']);
Route::post('/orders', [OrderController::class, 'store']);
});
// routes/central.php
Route::middleware(['central'])->prefix('central')->group(function () {
Route::get('/tenants', [TenantController::class, 'index']);
Route::post('/tenants', [TenantController::class, 'store']);
});
return Application::configure(basePath: dirname(__DIR__))
->withRouting(function () {
Route::middleware('web')
->prefix('central')
->name('central.')
->group(base_path('routes/central.php'));
Route::middleware(['web', 'tenant'])
->group(base_path('routes/tenant.php'));
})
->create();
All models live in app/Models/. Central vs tenanted distinguished by traits/interfaces, not subdirectories.
<?php
declare(strict_types=1);
namespace App\Models;
use Stancl\Tenancy\Database\Models\Tenant as BaseTenant;
class Tenant extends BaseTenant
{
public function users(): HasMany
{
return $this->hasMany(User::class);
}
}
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Order extends Model
{
// Automatically scoped to current tenant
// No tenant_id needed in queries
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}
Jobs must preserve tenant context when queued.
<?php
declare(strict_types=1);
namespace App\Jobs\Tenanted;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Stancl\Tenancy\Contracts\TenantWithDatabase;
use Stancl\Tenancy\Jobs\TenantAwareJob;
class ProcessOrderJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, TenantAwareJob;
public function __construct(
public TenantWithDatabase $tenant,
public OrderData $orderData,
) {
$this->onQueue('orders');
}
public function handle(ProcessOrderAction $action): void
{
// Runs in tenant context automatically
$action($this->orderData);
}
}
Dispatching:
ProcessOrderJob::dispatch(Landlord::tenant(), $orderData);
// Using Landlord's eachTenant helper
resolve(Landlord::class)->eachTenant(function () {
Order::where('status', 'pending')->update(['processed' => true]);
});
// Or manually for specific tenants
$tenants = Tenant::all();
foreach ($tenants as $tenant) {
resolve(Landlord::class)->runAs($tenant, function () {
Order::where('status', 'pending')->update(['processed' => true]);
});
}
Landlord::runAsCentral(function () {
$allTenants = Tenant::all();
});
if (Landlord::tenant() !== null) {
$orders = Order::all(); // Scoped to tenant
} else {
$tenants = Tenant::all(); // Central
}
→ Complete testing guide: tenancy-testing.md
Includes:
app/Models/ following Laravel convention