Installation
Meet Sprout
Sprout is an easy to use multitenancy solution that integrates seamlessly into your Laravel application, whether it's a pre-existing project, or a brand new one. It's designed to be simple to use, and easy to understand, while still providing the flexibility you need to build your application the way you want.
Multitenancy can be a complex problem to solve, with many different approaches. Sprout aims to support as many of these approaches as possible, in a way that takes as much of the complexity out of your hands as possible. It does this by coming with a set of defaults that you can use out of the box, but also allows you to customize and extend it to fit your needs.
Making Laravel Multitenanted
To make Laravel multitenanted, you'll first need a Laravel application, or at the very least, a fresh install. If you don't have either of these, check out the Laravel documentation, and then come back.
Installing Sprout
Once you have your Laravel application, you'll need to install Sprout, which can be done using composer.
1composer require sprout/sprout
Sprout is configured to make use of Laravel's package auto-discovery, so its service provider should be automatically
registered.
If you have this disabled, it's not working for some reason, or you'd like to manually control where Sprout is loaded,
you can manually register it in bootstrap/app.php
, before your AppServiceProvider
.
1return [2 Sprout\SproutServiceProvider::class,3 App\Providers\AppServiceProvider::class,4];
Sprout also comes with a handful of configuration files, which will need to be published using the following command.
1php artisan vendor:publish --provider="Sprout\SproutServiceProvider"
Creating your Tenant
Now that you have Sprout installed, you can get started by creating your applications Tenant. Sprout supports using Eloquent, or the database out the box, with the ability to extend it to support other methods. However, for this example, we'll assume that you're using Eloquent.
To create your tenant, you'll need a model, whether that's one that already exists, or a new one.
The model can be called whatever you like,
but I would recommend a name that makes sense for your application, such as Company
, Organization
, or Team
.
For this example, we'll be using Blog
.
The model will need a primary key,
and an attribute that contains
the tenant identifier, which we'll assume is called identifier
.
Once it has these, all you need to do is add the Tenant
interface and the IsTenant
trait to the model.
1namespace App\Models; 2 3use Illuminate\Database\Eloquent\Model; 4use Sprout\Contracts\Tenant; 5use Sprout\Database\Eloquent\Concerns\IsTenant; 6 7class Blog extends Model implements Tenant 8{ 9 use IsTenant;10}
If you want to use a different attribute other than identifier
for the tenant identifier, you can find out how to
go about that here.
With the model setup and configured as a tenant, you'll want to open up config/multitenancy.php
and configure your
tenancy to use the new tenant model.
This config file provides a number of options, but comes with sensible defaults that should work for most applications.
All you need to do right now is change the model in the tenants
provider, to use your new model.
1'providers' => [2 'tenants' => [3 'driver' => 'eloquent',4 'model' => \Sprout\Database\Eloquent\Tenant::class, 5 'model' => \App\Models\Blog::class, 6 ],7],
Registering Tenant Routes
The Next thing to do is register your routes that should be multitenanted. Before you can do that, you'll need to decide how your tenant will be identified. For this example, we'll be using subdomains which Sprout is configured to use by default, but it also supports using paths, headers, the session and cookies.
To use subdomains, you'll need to first tell the application what the main domain is, which can be done using the
TENANTED_DOMAIN
environment variable.
1TENANTED_DOMAIN=localhost
Finally, you can open your routes/web.php
file and add your tenant routes using the Sprout route macro.
1Route::tenanted(function () {2 Route::get('/', function () {3 return view('welcome');4 });5})
If you're using the subdomain
resolver, and you intend to have routes within your application that are not
multitenanted, you'll want to wrap those routes in a route group that uses the main applications domain.
If you don't do this, those routes will also be available under tenants subdomains, which can cause issues.
Read more about it here.
Overriding Laravel
Sprout comes with a number of service overrides, all of which are registered in
config/sprout/overrides.php
.
The default tenancy configuration is configured to use all the overrides, which can be changed, but is
fine for now.
One of these overrides is the session override,
which makes all sessions specific to the tenant they were created under.
By default, Laravel is set to use the database
session driver, and it creates the table in a
default migration
that comes with the Laravel installation.
If you plan to continue with this driver, you'll need to add two columns to the sessions
table.
1$table->string('tenancy')->nullable();2$table->bigInt('tenant_id')->nullable();
The tenant_id
column should match the primary key of your tenant model, which by default within Laravel would
be BIGINT
, which is why bigInt()
is used here.
The only other thing to consider here is that if you encounter CSRF issues when you first get started, clear your cookies in your browser, and it'll fix the issue. Unfortunately, this is a bug to do with Laravel's cookie handling if you access a CSRF protected route before Sprout is fully setup and working. Sadly, it's not something that Sprout can fix.
Tenant Child Models
You've got your tenant, Laravel knows how to identify it, and which routes should be multitenanted.
Everything is all working and ready to go, except your tenant model needs some child models.
Since the tenant is Blog
, it makes sense that one of its child models would be Post
.
1namespace App\Models; 2 3use Illuminate\Database\Eloquent\Model; 4use Illuminate\Database\Eloquent\Relations\BelongsTo; 5 6class Post extends Model 7{ 8 public function blog(): BelongsTo 9 {10 return $this->belongsTo(Blog::class);11 }12}
All that we need to do to hook this model into the multitenancy functionality is add the BelongsToTenant
trait,
and mark the tenant relation with the TenantRelation
attribute.
1namespace App\Models; 2 3use Illuminate\Database\Eloquent\Model; 4use Illuminate\Database\Eloquent\Relations\BelongsTo; 5use Sprout\Attributes\TenantRelation; 6use Sprout\Database\Eloquent\Concerns\BelongsToTenant; 7 8class Post extends Model 9{10 use BelongsToTenant; 11 12 #[TenantRelation] 13 public function blog(): BelongsTo14 {15 return $this->belongsTo(Blog::class);16 }17}
This model is now part of the multitenancy functionality.
All Post
that are loaded will automatically be scoped to the current Blog
, and any new Post
created will
automatically be associated with the current Blog
.
It may be worth creating a base abstract BlogModel
that defines your tenant relation and uses the trait, to
avoid repeating yourself.
Then all your child models can extend it and inherit the multitenancy configuration.
What's next?
You've now got a multitenanted Laravel application, and you can get on with building it. It's possible for you to carry on without ever touching Sprout again, besides the odd model trait here or there. However, there is a lot more that's available to you, so at the very least, it's worth exploring what Sprout has to offer.
Configuration
Sprout comes with a couple of configuration files, that allow you to customise how Sprout interacts with your application, and how it works at its core. Most of the configuration can be left as is, but it's worth exploring what it's all for.
Tenant Resolution
Sprout comes with a number of resolvers that can be used to identify your tenant, as well as the ability to create your own. It's worth exploring these, so you can figure out which ones you need, as Sprout lets you use as many as you need.
Service Overrides
Sprout comes with several service overrides that allow you to make Laravels core services tenant-aware, with the ability to create your own. Read about the ones that are enabled by default, how you can best use them.