Getting started
This page will go over installing the package and building a simple multi-domain multi-database demo, where each tenant has different users.
It’s recommended to follow this page in a fresh Laravel 11 application first, so that it’s easier for you to follow and make sense of the steps described here. After that, you should be able to confidently implement the package into an existing application.
Installing the package
To install the package, require it using composer:
composer require stancl/tenancy:dev-masterThen, run the tenancy:install command:
php artisan tenancy:installThat will create the following files:
Directoryapp
DirectoryProviders
- TenancyServiceProvider.php event listeners and route registration
Directoryconfig
- tenancy.php where the package behavior is configured
Directorydatabase
Directorymigrations
- 2019_09_15_000010_create_tenants_table.php
- 2019_09_15_000020_create_domains_table.php
Directoryroutes
- tenant.php
The three highlighted files (of the five total) are required. The others can sometimes be removed. We’ll cover that later.
After these files are created, add TenancyServiceProvider to your bootstrap/providers.php array, directly below AppServiceProvider:
return [ App\Providers\AppServiceProvider::class, App\Providers\TenancyServiceProvider::class,];With this, the package is installed. However, to demonstrate usage, we should do a few more things.
You can feel free to revert or change these, but I recommend following this guide in full to get a working multi-tenant app before you start customizing things.
The setup we’ll be using here is multi-domain multi-tenant. It’s what works best for the vast majority of applications, and is also the simplest, so it works great for our purposes here.
Setting up routing
Without any changes, Laravel is registering your web.php routes in bootstrap/app.php, and your tenant.php routes are being registered in your TenancyServiceProvider. However, there’s an edge case: which route should be used on a path defined in both web.php and tenant.php — such as /?
Route::get('/', function () { return view('welcome');});Route::get('/', function () { return 'This is your multi-tenant application. The id of the current tenant is ' . tenant('id');});To solve this, we need to scope the routes to their domains. tenant.php is already doing this using the PreventAccessFromUnwantedDomains middleware which prevents access from any domains defined in the tenancy.identification.central_domains config. But we also need to scope the routes in web.php.
There are two ways to do this. The first is to wrap your web.php routes in a loop like this:
foreach (config('tenancy.identification.central_domains') as $domain) { Route::domain($domain)->group(function () { // your actual routes });}The other approach is a bit more complicated but will result in simpler route files. See Routing for more details.
Running migrations
The tenancy:install command we ran earlier created two migrations. Let’s run them:
php artisan migrateNow that we have the tenants and domains tables created, let’s configure our Tenant model.
Creating a Tenant model
Create a new model:
<?php
namespace App\Models;
use Stancl\Tenancy\Database\Models\Tenant as BaseTenant;use Stancl\Tenancy\Database\Concerns\HasDatabase;use Stancl\Tenancy\Database\Concerns\HasDomains;use Stancl\Tenancy\Database\Contracts\TenantWithDatabase;
class Tenant extends BaseTenant implements TenantWithDatabase{ use HasDatabase, HasDomains;}And configure it as the tenant model the package should use:
return [ /** * Configuration for the models used by Tenancy. */ 'models' => [ 'tenant' => App\Models\Tenant::class,Adding a tenant migration
Let’s copy the create_users_table migration to the database/migrations/tenant folder, which got created automatically when you ran php artisan tenancy:install:
cp database/migrations/0001_01_01_000000_create_users_table.php \ database/migrations/tenant/0001_01_01_000000_create_users_table.phpIn a fresh Laravel 11 application, your database/migrations should now look like this:
Directorydatabase/migrations
- 0001_01_01_000000_create_users_table.php
- 0001_01_01_000001_create_cache_table.php
- 0001_01_01_000002_create_jobs_table.php
- 2019_09_15_000010_create_tenants_table.php
- 2019_09_15_000020_create_domains_table.php
Directorytenant
- 0001_01_01_000000_create_users_table.php
Now that we’ve moved the migration to the database/migrations/tenant folder, the tables defined in that migration will get created whenever a tenant is created.
Creating a sample tenant
Now that our Tenant model is created and configured, let’s go ahead and use it to create an actual tenant:
$tenant = App\Models\Tenant::create();$tenant->createDomain('tenant1.example.test');Now that we’ve created our tenant along with its domain, we can open it in the browser:
example.testshould serve your/route fromweb.php(the Laravel welcome page by default)tenant1.example.testshould show:This is your multi-tenant application. The id of the current tenant is ...
The output demonstrates that the tenant was identified using the domain, but let’s also verify that the tenant is using their own database:
Route::get('/', function () { dd(\DB::connection()->getDatabaseName()); return 'This is your multi-tenant application. The id of the current tenant is ' . tenant('id');});If you visit tenant1.example.test again, you should see tenant followed by a UUID.
Now let’s try to add some data to the tenant database:
$tenant = App\Models\Tenant::first();tenancy()->initialize($tenant);
App\Models\User::create(['name' => 'foo', 'email' => 'foo@example.test', 'password' => bcrypt('password')]);
$tenant2 = App\Models\Tenant::create();$tenant2->createDomain('tenant2.example.test'); // make sure the domain is correcttenancy()->initialize($tenant2);App\Models\User::create(['name' => 'bar', 'email' => 'bar@example.test', 'password' => bcrypt('password')]);
tenancy()->end();App\Models\User::create(['name' => 'baz', 'email' => 'baz@example.test', 'password' => bcrypt('password')]);Let’s change the route files:
Route::get('/', function () { dd(\DB::connection()->getDatabaseName()); dd(\App\Models\User::first()->email); return 'This is your multi-tenant application. The id of the current tenant is ' . tenant('id');});Route::get('/', function () { return view('welcome'); dd(\App\Models\User::first()->email);});Now you should see:
foo@example.testontenant1.example.testbar@example.testontenant2.example.testbaz@example.testonexample.test(central domain)
And with that, you have a fully working multi-domain multi-database application!
To learn how to configure the package for your exact needs, make sure to read these pages next: