Multi-Database Tenancy
SaaSykit Tenancy comes with a plugin that enables multi-database tenancy.
FilamentPHP doesn't support multi-database tenancy out of the box, However, you can use the Filament Tenancy for Laravel package to add multi-database tenancy support to your FilamentPHP application.
How Filament Tenancy For Laravel worksโ
Filament Tenancy For Laravel integrates the famous Tenancy for Laravel package with FilamentPHP to enable multi-database tenancy in your FilamentPHP panel.
Tenancy for Laravel package is one of the most flexible and feature-rich multi-database tenancy packages for Laravel, it allows you to create a separate database for each tenant in your application, and it allows you to also separate cache, filesystem, and other services for each tenant if you want.
A Word of Cautionโ
Using multi-database tenancy increases the complexity of your application, and it requires that you take care of many other things like migrating databases for tenants, context switching between tenants when debugging issues, etc. So if you don't really have a strong valid reason to use multi-database tenancy, it's recommended to stick with single-database tenancy, which is simpler and easier to manage.
Installationโ
To use the plugin, you'd need to add it to your project via composer:
- First you need to include the repository in your
composer.json
file as this is a private package:
"repositories": [
{
"type": "vcs",
"url": "https://code.saasykit.com/saasykit/filament-tenancy-for-laravel.git"
}
]
- Then, you can install the package via composer:
composer require saasykit/filament-tenancy-for-laravel
- Once the package is installed, you'll need to run the installation command
php artisan filament-tenancy-for-laravel:run-installer
This command will publish a configuration file (tenancy.php
), a Service Provider (TenancyServiceProvider
), route/tenant.php
route file, and also creates "database/migrations/tenant" directory where the tenant-specific migrations will be stored.
The command will also ask you whether to modify the existing models to make them load from the central database (which is a requirement, since the existing models that ship with SaaSykit Tenancy will have to be served from the central database (shared), while you can introduce tenant-specific models (database tables) later after the plugin installation is complete).
Please write yes
to modify the models.
- Register the service provider in
bootstrap/providers.php
return [
// ...
App\Providers\TenancyServiceProvider::class, // <-- add it here at the end of the array
];
- Add the plugin to the FilamentPHP panel that has tenancy (
app/Providers/Filament/DashboardPanelProvider.php
in SaaSykit Tenancy)
// app/Providers/Filament/DashboardPanelProvider.php
use App\Providers\TenancyServiceProvider; // <-- important to use the TenancyServiceProvider added by the plugin installer into your application, not the one in Stancl
use Saasykit\FilamentTenancyForLaravel\FilamentTenancyForLaravelPlugin;
// ...
public function panel(Panel $panel): Panel
{
return $panel->
// ... some configurations here
->plugins([
....
FilamentTenancyForLaravelPlugin::make()->identificationMiddleware(TenancyServiceProvider::TENANCY_INITIALIZER),
])
}
- Add the following in
bootstrap\app.php
->withMiddleware(function (Middleware $middleware) {
$middleware->group('universal', [
App\Providers\TenancyServiceProvider::TENANCY_INITIALIZER,
]);
}) // you can attach that to the call chain in that file
- Adjust the Tenant model (in
app/Models/Tenant.php
) to include the following Traits & functions:
use Stancl\Tenancy\Contracts\TenantWithDatabase;
use Stancl\Tenancy\Database\Concerns\HasDatabase;
use Stancl\Tenancy\Database\TenantCollection;
use Stancl\Tenancy\Events;
use Stancl\Tenancy\Database\Concerns;
class Tenant extends Model implements TenantWithDatabase
{
use HasDatabase;
use Concerns\CentralConnection,
Concerns\GeneratesIds,
Concerns\HasDataColumn,
Concerns\HasInternalKeys,
Concerns\TenantRun,
Concerns\InvalidatesResolverCache;
public static function getCustomColumns(): array
{
return [
'name',
'uuid',
'is_name_auto_generated',
'created_by',
'domain'
];
}
public function getTenantKeyName(): string
{
return 'uuid';
}
public function getTenantKey()
{
return $this->getAttribute($this->getTenantKeyName());
}
public function newCollection(array $models = []): TenantCollection
{
return new TenantCollection($models);
}
protected $dispatchesEvents = [
'saving' => Events\SavingTenant::class,
'saved' => Events\TenantSaved::class,
'creating' => Events\CreatingTenant::class,
'created' => Events\TenantCreated::class,
'updating' => Events\UpdatingTenant::class,
'updated' => Events\TenantUpdated::class,
'deleting' => Events\DeletingTenant::class,
'deleted' => Events\TenantDeleted::class,
];
// ... the rest of the model goes here
}
Please note that the getCustomColumns()
function above should contain all the columns that you want to be stored in the tenant's database. This is because the Tenancy for Laravel package comes with a "Virtual column" feature, which will store values in a json column in the tenant's database unless they are listed in the getCustomColumns()
function.
Basically, all the columns that are defined in the $fillable
array in the model should be listed in the getCustomColumns()
function.
And that's it! You now have multi-database tenancy enabled in your FilamentPHP application! ๐
Now if you visit the customer dashboard, it should already be working.
Defining Tenant-specific Modelsโ
As mentioned above, FilamentPhp doesn't support multi-database tenancy out of the box, so it will always try to scope resources (models) to tenants (by looking for a tenant_id
column in the resource's table).
That will be okay for models that live in the "central database" like all the models that come with SaaSykit Tenancy, but for tenant-specific models that will live in the tenant's database, you will not really have a tenant_id
column in the table (as you don't need it, since the table is already scoped to the tenant).
So in this case you will need to tell FilamentPHP that this model (resource) is not scoped to tenants, and leave the Tenancy for Laravel to resolve the tenant context and load the model from the correct database of the tenant.
To do that, add the following line to any model that you want to be tenant-specific:
// app/Filament/Dashboard/Resources/XyzResource.php
class XyzResource extends Resource {
protected static bool $isScopedToTenant = false;
// ...
}
Tenant Identification Middlewareโ
The FilamentTenancyForLaravelPlugin
plugin comes with 2 middlewares that are used to identify the tenant from the request and set the tenant context (which build on top of Tenancy for Laravel's middlewares).
The middlewares are:
Saasykit\FilamentTenancyForLaravel\Middleware\InitializeTenancyByUuid
which identifies the tenant by theuuid
in the request. This is the default middleware that is used in the plugin and follows the default FilamentPhp way of adding the uuid of the tenant in the URL and using that to resolve the current tenant.Saasykit\FilamentTenancyForLaravel\Middleware\InitializeTenancyByDomain
which identifies the tenant by thedomain
in the request. This is an alternative middleware that you can use if you want to identify the tenant by the domain name instead of the uuid (will use thetenant.domain
column in the tenant's table).
You can set the middleware that you want to use in the App\Providers\TenancyServiceProvider::TENANCY_INITIALIZER
constant.
// app/Providers/TenancyServiceProvider.php
class TenancyServiceProvider extends ServiceProvider
{
public const TENANCY_INITIALIZER = Saasykit\FilamentTenancyForLaravel\Middleware\InitializeTenancyByUuid::class;
// ...
}
Identifying Tenants by Domainโ
If you want to identify tenants by domain, you can use the InitializeTenancyByDomain
middleware instead of the InitializeTenancyByUuid
middleware.
- To do that, you can set the
TENANCY_INITIALIZER
constant in theTenancyServiceProvider
toInitializeTenancyByDomain::class
.
// app/Providers/TenancyServiceProvider.php
class TenancyServiceProvider extends ServiceProvider
{
public const TENANCY_INITIALIZER = Saasykit\FilamentTenancyForLaravel\Middleware\InitializeTenancyByDomain::class;
// ...
}
- You will also need to change the tenancy function calls in the
app/Providers/Filament/DashboardPanelProvider.php
to use the domain instead of the uuid.
->plugins([
....
FilamentTenancyForLaravelPlugin::make()->identificationMiddleware(TenancyServiceProvider::TENANCY_INITIALIZER),
])
// ...
->tenantDomain('{tenant:domain}')
->tenant(Tenant::class, 'domain');
Now you need to make sure that the tenant's domain column is filled with the correct domain name of the tenant (you need to add that into the flow of registering tenants in your application).
And that's it! You now have multi-database tenancy enabled in your FilamentPHP application! ๐
Using domains to resolve your tenants adds one more layer of complexity to your application, because a user can have multiple tenants and they can switch the tenant from the tenant dropdown in the dashboard. By default, the user will land on a login screen if they switch the domain, so you might need to allow wildcard session login in your application to allow the user to be logged in to multiple tenant domains at the same time, and easily switch between them.
In this case, change your SESSION_DOMAIN
in your .env
file to something like SESSION_DOMAIN=.yourdomain.com
to allow wildcard session logging for any subdomain of your domain.
Going live (Production)โ
As the plugin is private, you might find it useful to fork the plugin and host it on your own Github repository. You can then include the repository in your composer.json
file like this:
"repositories": [
{
"type": "vcs",
"url": "[email protected]:{YOUR_GITHUB_USERNAME}/filament-tenancy-for-laravel.git"
}
]
Further Readingโ
I highly recommend that check the documentation of the Tenancy for Laravel package to understand how multi-database tenancy works and how you can use it and adapt it to your application.