Skip to content

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:

  1. Query filtering -- Adds a global scope that appends WHERE tenant_id = ? to every query on the model, using the current tenant from the container.
  2. Auto-assignment -- When creating a new record, automatically sets tenant_id to the current tenant's ID if not already set.
  3. 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,
];