Single-Database
Introduction
In single-database mode, all tenants share one database. Data isolation is enforced through the TenantScope trait -- a global scope that automatically filters queries by tenant_id and auto-sets tenant_id on new records.
TenantScope Trait
The TenantScope trait is applied to models that need tenant isolation (e.g., sub-organization models like Team or Workspace when they exist within a tenant). The Tenant model itself does not use this trait.
php
trait TenantScope
{
public static function bootTenantScope(): void
{
static::addGlobalScope('tenant', function (Builder $builder): void {
$tenant = app()->bound('current_tenant') ? app('current_tenant') : null;
if ($tenant instanceof Tenant) {
$builder->where($builder->getModel()->getTable() . '.tenant_id', $tenant->id);
}
});
static::creating(function (Model $model): void {
$tenant = app()->bound('current_tenant') ? app('current_tenant') : null;
if ($tenant instanceof Tenant && !$model->getAttribute('tenant_id')) {
$model->setAttribute('tenant_id', $tenant->id);
}
});
}
public function tenant(): BelongsTo
{
return $this->belongsTo(Tenant::class);
}
public static function withoutTenantScope(): Builder
{
return static::query()->withoutGlobalScope('tenant');
}
}The trait does three things:
- Query filtering -- Adds a global scope that appends
WHERE tenant_id = ?to every query on the model, using the current tenant from the container. - Auto-assignment -- When creating a new record, automatically sets
tenant_idto the current tenant's ID if not already set. - Bypass -- Provides
withoutTenantScope()to query across all tenants when needed (e.g., admin panels, background jobs).
Configuration
php
return [
'organization_type' => 'tenant',
'organization_model' => \App\Models\Tenant::class,
'database_strategy' => 'single',
'sub_organization' => null,
'has_sub_teams' => false,
];