Courses

Laravel 12 Multi-Tenancy: All You Need To Know

archtechx / tenancy: Filter Records and Protect Subdomains

Summary of this lesson:
- Adding tenant_id to projects table
- Using BelongsToTenant trait for automatic filtering
- Implementing BelongsToPrimaryModel for related models
- Adding subdomain access protection middleware

In the second part of reviewing tenancy for Laravel for a single database, let's add the tenant_id to the projects table, and I will show you what this package does brilliantly to filter the records.

database/migrations/xxx_add_tenant_id_to_projects_table.php:

Schema::table('projects', function (Blueprint $table) {
$table->foreignId('tenant_id')->constrained();
});

app/Models/Project.php:

class Project extends Model
{
protected $fillable = [
'name',
'tenant_id',
];
}

Filter Records

In the previous lessons of this course, we added global scopes or traits to fill tenant_id automatically and then filtered by that. So, this package does that for us.

In the documentation section of single database tenancy, you may find an example saying to add the BelongsToTenant trait on a primary Model, and all the tenant logic will happen automatically. It will assume this Model has a tenant_id field, which will be filled in automatically.

app/Models/Project.php:

use Stancl\Tenancy\Database\Concerns\BelongsToTenant;
 
class Project extends Model
{
use BelongsToTenant;
 
protected $fillable = [
'name',
'tenant_id',
];
}

But what if we need to get tasks? We don't have the tenant_id field in the tasks table. For such a case, we must add the BelongsToPrimaryModel trait on the Model and the getRelationshipToPrimaryModel() method to provide the relationship.

app/Models/Task.php:

use Stancl\Tenancy\Database\Concerns\BelongsToPrimaryModel;
 
class Task extends Model
{
use BelongsToPrimaryModel;
 
protected $fillable = [
'name',
'project_id',
];
 
public function getRelationshipToPrimaryModel(): string
{
return 'project';
}
 
public function project(): BelongsTo
{
return $this->belongsTo(Project::class);
}
}

Now, if I create a project and task with one user.

I also registered with a new one but don't see any projects or tasks. This is a brilliant work of the package author; how easy it is to filter any Eloquent model by tenant.


Protect Subdomains

Lastly, we need to add a security check. The currently logged-in users subdomain is qyz, which doesn't have any projects or tasks.

But what if I go to other users' subdomains? Then, I can see projects and tasks from a tenant to which this user doesn't belong.

Luckily, with this package, we only need to add a Middleware.

routes/tenant.php:

Route::middleware([
'web',
InitializeTenancyByDomain::class,
PreventAccessFromCentralDomains::class,
\Stancl\Tenancy\Middleware\ScopeSessions::class,
])->group(function () {
// ...
});

Now, if I try to enter the tenant I don't belong to, I get a forbidden message.


Other features related to Tenancy, which we did in the previous lessons, are identical. They don't depend on the package at all. If you still need to check those lessons, a single database with no package, you can check them now. I will not repeat the same thing for this package.

That's the general logic for a lot of packages. They provide some API for the functionality, but then you still add your custom application logic on top using that package's helpers, features, and structure.

So, that's the overview of this package for a single database for now. Let's review another package for a single database, and then we will go to a separate section on multi-database functionality.

You can find the source code for the single database example using the stancl/tenancy package on GitHub.

Previous: archtechx / tenancy: Installation, Configuration and Register Tenant
avatar

Hi, thanks for your amazing tutotial. i have completed everything while i'm using jetstream. but i see that even after comment the bootstraper its still create new databases .. e.g: (tenant1,tenant2..etc). Kindly advise.

avatar

Sorry I can't debug it for you in the comment here and blindly guess what happened wrong, it requires actual debugging process.

avatar

No worries, i found the solution. but plz make videos using jetstream not breeze, thanks.

avatar

Sorry, but not planning to, for many people Jetstream is an overkill system, Breeze is much more simple to understand.

avatar
You can use Markdown
avatar

archtechx/tenancy in v3 has Stancl\Tenancy\Middleware\ScopeSessions

Route::middleware([
    'web',
    'auth',
    InitializeTenancyByDomainOrSubdomain::class,
    PreventAccessFromCentralDomains::class,	
    \Stancl\Tenancy\Middleware\ScopeSessions::class  
])->group(function () {
👍 4
avatar
You can use Markdown
avatar

I've really enjoyed your course. Any chance you could add archtechx / tenancy using path identification instead of subdomain identification? That would be amazing! For branding purposes, it would be nice to have users login from the same path (ie. brandedsomain.test/login), then automatically get redirected to their dashboard using a route prefix like brandeddomain.test/{portal}/dashboard. If they have more than one portal perhaps they could switch it similar to the way the Jetstream Teams are swiched by the user in the panel.

avatar

Great to hear you enjoyed the course.

For now, we're not planning to expand this course, but we'll see when we get to updating it to Laravel 12, maybe more topics will appear like the one you're suggesting.

avatar
You can use Markdown
avatar
You can use Markdown