Add Multi-Tenancy to Laravel Filament: Simple Way

Multi-tenancy is pretty common in web projects - when you want to give records access only to users who created those records. In other words, everyone manages their data and doesn't see anyone else's data. This article will show you how to implement it in the most simple way, with a single database, in a Filament admin panel.

In fact, this approach comes from general Laravel, so can be implemented in any Laravel project, we're just showcasing the Filament example.


What We're Creating Here

Imagine a web project to manage Tasks, and every user may see only their own entered tasks.

tasks app


Step 1. Logging User Who Created The Record

For all the models that we want to be multi-tenanted, we need an extra field in their DB table.

php artisan make:migration add_user_id_to_tasks_table

And then migration code:

return new class extends Migration {
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->boolean('is_admin')->default(false)->after('password');
});
}
};

And then app/Models/Task.php addition - see the last line:

class Task extends Model
{
use Multitenantable;
 
protected $fillable = [
'title',
'user_id'
];
}

Now, how do we fill in that field automatically? We could do that with Model Observer, but for this case let's create a Trait, which you will be able to reuse for other Models.

We're creating a new folder app/Models/Traits and file app/Models/Traits/Multitenantable.php:

trait Multitenantable
{
protected static function bootMultitenantable(): void
{
static::creating(function ($model) {
$model->user_id = auth()->id();
});
}
}

Notice: Laravel doesn't have make:trait Artisan command, you need to create the file manually.

Notice the method name bootMultitenantable() - name convention bootXYZ() means Laravel will automatically launch this method when a trait is used. You can call it a trait "constructor".

So, what we're doing here? We add authenticated user ID to the field user_id, in whatever Model that is.

Notice, nowhere here do we mention Task or any other model name. So, we can add this trait to however many models we want.

Let's add this Trait - just two new lines of code in app/Models/Task.php:

use App\Models\Traits\Multitenantable;
 
class Task extends Model
{
use Multitenantable;
// ...
}

And that's it, field user_id will be filled in automatically when you create a task.

tasks db values


Step 2. Filtering Data By User

The next step is filtering data whenever someone accesses the Tasks list or tries to get the Task by its ID field.

We need to add Global Scope on all the queries on that model. And it's also possible to do it flexibly in the same app/Models/Traits/Multitenantable.php. Here's the full code:

use Illuminate\Database\Eloquent\Builder;
 
trait Multitenantable
{
protected static function bootMultitenantable(): void
{
static::creating(function ($model) {
$model->user_id = auth()->id();
});
 
static::addGlobalScope('created_by_user_id', function (Builder $builder) {
$builder->where('user_id', auth()->id());
});
}
}

We've included Eloquent/Builder class here, and then used static::addGlobalScope() to filter any query with the currently logged-in user.

That's it, every user will see only their tasks, but it will also work for other Models in the future if they use the same Trait.


Step 3. Administrator To See All Entries?

Some "super user" of the system should still see all tasks, right? Now, this functionality will depend on your user-role-permission implementation, but generally, I will show you the place where you can add this permission.

In the Trait's filter, we should add this if-statement:

trait Multitenantable
{
protected static function bootMultitenantable(): void
{
static::creating(function ($model) {
$model->user_id = auth()->id();
});
 
if (! auth()->user()->is_admin) {
static::addGlobalScope('created_by_user_id', function (Builder $builder) {
$builder->where('user_id', auth()->id());
});
}
}
}

Yes, it's that simple - just add an if-statement for those users who are exceptions for the filter.


Step 4. Filament Column

In this tutorial, we use Filament admin. It would be nice if the admin user would see who the task belongs to, in the table. It's very easy: for the desired column you just need to use the visible() method.

app/Filament/Resources/TaskResource.php:

class TaskResource extends Resource
{
// ...
public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('title')
->searchable()
->sortable(),
TextColumn::make('user.name')
->searchable()
->sortable()
->visible(fn () => auth()->user()->is_admin),
]);
}
// ...
}

In this case, don't forget to add a relation for the Task model.

app/Models/Task.php:

use Illuminate\Database\Eloquent\Relations\BelongsTo;
 
class Task extends Model
{
use Multitenantable;
 
protected $fillable = [
'title',
'user_id'
];
 
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}

So, this is how easy it is to implement multi-tenancy in Laravel in a simple way, with Filament.

But there are more complex scenarios, and I showed them in my 2-hour course Laravel Multi-Tenancy: All You Need To Know.

avatar

Thank you so much for this great explanation, Please add more Filament tutorials. It really makes bulding admin dashboard easir espilalcy for newbie like me.

πŸ₯³ 1
😍 1
avatar

Suggest topics :)

πŸ‘ 1
avatar

I shared some ideas with Povilas in Twitter, I hope we can see some advanced tutorials soon. https://twitter.com/ukcbmk1/status/1635319511359582209?t=sZuwCOkLQvl-js0M8rqH_g&s=19

avatar
Wemerson Couto GuimarΓ£es

Amazing article! Congrats!

An effective solution for a simple multitenancy structure.

Like our articles?

Become a Premium Member for $129/year or $29/month

Recent Premium Tutorials