Multitenancy in Laravel

Mirza Waleed

Mirza Waleed

Share:

Global scopes allow isolating data by tenant automatically. Dedicated tenant models keep customer information securely separated.

Database schema design supports partitioning data into separate databases or tables for each customer. Global helpers seamlessly switch contexts between tenants.

Tenant-aware controllers check authentication and permissions scoped to individual customers. Isolated brandings, assets, and customizations give each their own unique experience from shared code.

Testing helpers seed demo data suited to multitenant apps. Migrations deployed per tenant establish independent data changes.

This post dives into practical implementations using Laravel’s multitenancy-ready features. From architectural pattern guides to concrete code examples, explore proven strategies that scale your solutions efficiently for many customers.

Database Schema for Supporting Multiple Tenants

There are two main approaches to structuring your database schema to support multiple tenants – using a separate database per tenant or partitioning data within a shared database.

For separate databases, each tenant gets their own isolated database instance. This provides ultimate separation but makes maintenance more complex.

For a shared database, you can either create a dedicated tenant table to store tenant information, or partition data at the table level by adding a tenant_id column to all relevant tables.

Partitioning at the table level is more efficient since it avoids the overhead of managing multiple database connections. To identify tenant-specific rows, all queries would be filtered by the tenant_id column.

// Add tenant_id to tables

$table->unsignedBigInteger('tenant_id');

$table->foreign('tenant_id')

->references('id')->on('tenants')

->onDelete('cascade');

 

Tenant Table to Store Tenant Info

If using a shared database, you’ll need a central tenant table to store basic information for each customer.

// tenants table

$table->id();

$table->string('name'); 

$table->string('domain')->unique();

$table->text('details')->nullable();

 

The tenant_id could then be used to reliably scope queries and segregate data per customer throughout your application.

Proper database structuring using these techniques ensures each tenant’s data remains isolated within your multitenant Laravel system.

 

The Tenant Model

The tenant model serves as the core entity representing each customer in the system. It stores essential metadata and establishes relationships to other tenant-specific data.

Tenant Model Fields

The minimum fields needed are:

// app/Models/Tenant.

class Tenant extends Model

{

protected $primaryKey = 'id';

protected $fillable = [

'name', 

'domain',

// other fields

];

}

 

Relations to Tenant Data

Relations associated with the tenant to custom content:

public function users() {

return $this->hasMany(User::class);

}

public function pages() {

return $this->hasMany(Page::class); 

}

 

 

Global Scopes for Tenant Filtering

Global scopes automatically filter queries by tenant:

public function scopeForTenant($query, $tenant) {

return $query->where('tenant_id', $tenant->id);

}

 

A base Tenant model with fields, relationships, and global scopes provides the foundation to isolate and access all tenant-specific information consistently throughout the application.

 

Tenant Aware Controllers

Controllers need awareness of the tenant context to filter responses appropriately.

Adding Tenant ID to Routes/Requests

A middleware can attach the tenant to the request, ie:

// Add tenant ID to requests

$request->merge(['tenant' => $tenant]);

 

Tenant Checks in Controller Actions

Validate tenant before executing logic:

public function index(Request $request)

{

$this->authorizeTenant($request->tenant);

// query for tenant

return User::forTenant($request->tenant)->get();

}

 

 

Accessing Tenant Data

Retrieve tenant from request to scope queries:

protected function authorizeTenant($tenant) 

{

$this->tenant = $tenant;

}

public function create(Request $request)

{

Page::create([

'title' => $request->title,

'tenant_id' => $this->tenant->id

]);

}

 

Actions can now rely on the attached tenant object to target correct multi-tenant data and responses for that customer.

Tenant Specific Data

Tenants will often need custom styling, configurations, or content unique to their needs. Migration namespaces help organize this data.

Namespaced Migrations

Migrations to create tenant fields live in a namespace:

artisan make:migration create_tenant_settings_table --path=Database/Tenants/Migrations

 

Approach for Shared vs Separate Data

Some data like user profiles can live together partitioned by tenant_id.

But tenant-specific configurations/files are best stored separately:

// Save settings to tenants' table

$tenant->settings = ['key' => 'value'];

$tenant->save();

// Save custom CSS to file for each tenant

File::put("public/css/tenants/{$tenant->id}.css", $css);

 

This trains developers to think of tenant data location – centralized or isolated. Proper structuring maintains a balance between flexibility and isolation.

Namespaced migrations and selective separation of tenant information implement a scalable strategy for multi-tenant databases in Laravel apps.

 

Tenant Specific Content

Each tenant can have their own unique content customized for their business needs.

Tenant Settings/Configuration Options

Tenants may need to configure application behavior:

// Get tenant settings 

$settings = $tenant->settings;

// Common settings stored in tenants table

$fillable = ['name', 'timezone', 'stylesheet'];

 

Tenant Branding/Customization

Allow custom logos, colors etc:

// Store logo image path 

$tenant->logo = '/uploads/'.$logo->hashName();

// Save custom stylesheet

File::put("CSS/{$tenant->id}.css", $customCSS);

 

Isolated Assets/Files for Each Tenant

Files like images & documents stay separated to avoid conflicts:

// Unique folder for each tenant

$assetPath = "files/{$tenant->id}/"; 

// Save & retrieve tenant file

Storage::put($assetPath.$fileName, $file);

$file = Storage::get($assetPath.$fileName);

Giving tenants independence over these areas within a shared application enhances their experience through a customized interface and content relevant only to their business needs and brand.

Authentication

Authentication methods must account for the tenant context to properly assign users.

Tenant-Aware Authentication Methods

During login, determine the user’s tenant:

// Get tenant on login

$tenant = Tenant::findByDomain(request('domain'));

// Authenticate for that tenant

Auth::guard('users')->attempt(['email','password'], $tenant);

 

Assigning Users to Tenants

Connect users to their tenants:

// User model

public function tenant(){

return $this->belongsTo(Tenant::class);

}

// Assign on user create

$user->tenant()->associate($tenant);

$user->save();

 

User Permissions Scoping by Tenant

Authorize actions within a tenant:

// Check permission 

$this->authorize('update-profile', $user->tenant);

// Policy method

public function updateProfile($tenant){

return $this->user->tenant_id == $tenant->id;

}

Proper user-tenant relationships and scoping permissions by tenant integrate authorization into the multitenant architecture.

Tenant Switching

Allow authenticated users to switch between tenants they have access to.

Tenant User Portal for Switching

Present a user portal to select tenants:

// User dashboard

@foreach($user->accessibleTenants as $tenant)

<a href="{{ route('switch-tenant', $tenant) }}">

{{ $tenant->name }}

</a>

@endforeach

 

Global Tenant Helper/Middleware

Attach new tenant to request on a switch:

// Middleware

public function handle($request, Closure $next)

{

$tenant = Tenant::find($request->route('tenant_id'));

session()->put('tenant_id', $tenant->id);

return $next($request);

}





// Helper 

function currentTenant() {

return Tenant::find(session('tenant_id'));

}

This maintains the correct tenant context across requests as the user navigates, allowing them to manage multiple customer environments through a single portal.

 

Testing Multitenant Apps

Proper test data setup is crucial for multi-tenant apps to evaluate each tenant in isolation.

Tenant Seeding for Testing

Seed a test tenant before each test:

// DatabaseSeeder

$tenant = Tenant::factory()->create();

$this->call(TenantDataSeeder::class, ['tenant' => $tenant]);

 

Isolating Test Data per Tenant

Seed tenant-specific data like users:

// TenantDataSeeder

public function run(Tenant $tenant)

{

User::factory()->count(5)->create([

'tenant_id' => $tenant->id

]);

}

Tests can then make assertions against isolated tenant data:

// User test

$tenant = Tenant::first();

$users = User::forTenant($tenant)->get();

$this->assertCount(5, $users);

Maintaining test isolation prevents data collisions between tenants. Helper methods also allow dynamic seeding per SUT.

Comprehensive testing ensures that multi-tenant applications function correctly for each unique tenant configuration, customization, and data set.

Conclusion

Laravel provides powerful features that allow developers to easily build robust multi-tenant applications. The framework’s tooling for scoping queries, isolating data, and tenant-aware patterns supports seamlessly managing multiple customers from a single codebase.

By leveraging techniques like dedicated tenant models, global scopes, migration namespaces, and request context binding, both data and user experiences can remain securely separated while sharing code. Developers gain a straightforward and consistent approach for filtering responses according to the tenant.

Proper database structuring through tenant tables or partitioning further strengthens each customer’s private domain. Combined with rating scoping to tenants, authentication falls naturally into the architecture.

Comprehensive tenant models also promote strong relationships and organization of tenant-specific information. Ultimately this leads to focused code, clear tenant identities, and flexible customizations unique to individual business needs.

Laravel’s built-in tenant utilities encourage disciplined multi-tenant designs capable of scaling complexity while retaining simplicity. With best practices for tenant switching, testing isolation, and content management applied, Endless Growth handles evolving business volumes seamlessly.

Ready To Start Your Project

OR