# Table of Contents - [Introduction | Tenancy for Laravel](#introduction-tenancy-for-laravel) - [Compared to other packages | Tenancy for Laravel](#compared-to-other-packages-tenancy-for-laravel) - [The two applications | Tenancy for Laravel](#the-two-applications-tenancy-for-laravel) - [Installation | Tenancy for Laravel](#installation-tenancy-for-laravel) - [Quickstart Tutorial | Tenancy for Laravel](#quickstart-tutorial-tenancy-for-laravel) - [Upgrading from 2.x | Tenancy for Laravel](#upgrading-from-2-x-tenancy-for-laravel) - [Configuration | Tenancy for Laravel](#configuration-tenancy-for-laravel) - [Domains | Tenancy for Laravel](#domains-tenancy-for-laravel) - [Tenants | Tenancy for Laravel](#tenants-tenancy-for-laravel) - [Routes | Tenancy for Laravel](#routes-tenancy-for-laravel) - [Event system | Tenancy for Laravel](#event-system-tenancy-for-laravel) - [Tenancy bootstrappers | Tenancy for Laravel](#tenancy-bootstrappers-tenancy-for-laravel) - [Optional features | Tenancy for Laravel](#optional-features-tenancy-for-laravel) - [Automatic tenancy mode | Tenancy for Laravel](#automatic-tenancy-mode-tenancy-for-laravel) - [Tenant-specific config | Tenancy for Laravel](#tenant-specific-config-tenancy-for-laravel) - [Telescope tags | Tenancy for Laravel](#telescope-tags-tenancy-for-laravel) - [Migrations | Tenancy for Laravel](#migrations-tenancy-for-laravel) - [Universal routes | Tenancy for Laravel](#universal-routes-tenancy-for-laravel) - [Early identification | Tenancy for Laravel](#early-identification-tenancy-for-laravel) - [Cross-domain redirect | Tenancy for Laravel](#cross-domain-redirect-tenancy-for-laravel) - [Vite bundler | Tenancy for Laravel](#vite-bundler-tenancy-for-laravel) - [Multi-database tenancy | Tenancy for Laravel](#multi-database-tenancy-tenancy-for-laravel) - [Customizing tenant databases | Tenancy for Laravel](#customizing-tenant-databases-tenancy-for-laravel) - [Session scoping | Tenancy for Laravel](#session-scoping-tenancy-for-laravel) - [Single-database tenancy | Tenancy for Laravel](#single-database-tenancy-tenancy-for-laravel) - [Tenant identification | Tenancy for Laravel](#tenant-identification-tenancy-for-laravel) - [User impersonation | Tenancy for Laravel](#user-impersonation-tenancy-for-laravel) - [Manual tenancy mode | Tenancy for Laravel](#manual-tenancy-mode-tenancy-for-laravel) - [Synced resources between tenants | Tenancy for Laravel](#synced-resources-between-tenants-tenancy-for-laravel) - [Integration with other packages | Tenancy for Laravel](#integration-with-other-packages-tenancy-for-laravel) - [Manual tenancy initialization | Tenancy for Laravel](#manual-tenancy-initialization-tenancy-for-laravel) - [Queues | Tenancy for Laravel](#queues-tenancy-for-laravel) - [Testing | Tenancy for Laravel](#testing-tenancy-for-laravel) - [Laravel Horizon integration | Tenancy for Laravel](#laravel-horizon-integration-tenancy-for-laravel) - [Integration with Spatie packages | Tenancy for Laravel](#integration-with-spatie-packages-tenancy-for-laravel) - [Laravel Sail integration | Tenancy for Laravel](#laravel-sail-integration-tenancy-for-laravel) - [Laravel Nova integration | Tenancy for Laravel](#laravel-nova-integration-tenancy-for-laravel) - [Laravel Passport integration | Tenancy for Laravel](#laravel-passport-integration-tenancy-for-laravel) - [Laravel Orchid integration | Tenancy for Laravel](#laravel-orchid-integration-tenancy-for-laravel) - [Laravel Telescope integration | Tenancy for Laravel](#laravel-telescope-integration-tenancy-for-laravel) - [Livewire integration | Tenancy for Laravel](#livewire-integration-tenancy-for-laravel) - [Tenant attribute encryption | Tenancy for Laravel](#tenant-attribute-encryption-tenancy-for-laravel) - [Console commands | Tenancy for Laravel](#console-commands-tenancy-for-laravel) - [Cached tenant lookup | Tenancy for Laravel](#cached-tenant-lookup-tenancy-for-laravel) - [Laravel Sanctum integration | Tenancy for Laravel](#laravel-sanctum-integration-tenancy-for-laravel) - [Tenant maintenance mode | Tenancy for Laravel](#tenant-maintenance-mode-tenancy-for-laravel) - [Real-time facades | Tenancy for Laravel](#real-time-facades-tenancy-for-laravel) - [Tenant-aware commands | Tenancy for Laravel](#tenant-aware-commands-tenancy-for-laravel) --- # Introduction | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Introduction ============ What is multi-tenancy? ---------------------- Multi-tenancy is the ability to provide your service to multiple users (tenants) from a single hosted instance of the application. This is contrasted with deploying the application separately for each user. You may find this talk insightful: [https://multitenantlaravel.com/](https://multitenantlaravel.com/) . Simply going through the slides will give you 80% of the value of the talk in under five minutes. Note that if you just want to, say, scope todo tasks to the current user, there's no need to use a multi-tenancy package. Just use calls like `auth()->user()->tasks()`. This is the simplest form of multi-tenancy. This package is built around the idea that multi-tenancy usually means letting tenants have their own users which have their own resources, e.g. todo tasks. Not just users having tasks. Types of multi-tenancy ---------------------- There are two **types** of multi-tenancy: * single-database tenancy — tenants share one database and their data is separated using e.g. `where tenant_id = 1` clauses. * multi-database tenancy — each tenant has his own database This package lets you do both, though it focuses more on multi-database tenancy because that type requires more work on the side of the package and less work on your side. Whereas for single-database tenancy you're provided with a class that keeps track of the current tenant and model traits — and the rest is up to you. Modes of multi-tenancy ---------------------- The tenancy "mode" is a unique property of this package. In previous versions, this package was intended primarily for [automatic tenancy](https://tenancyforlaravel.com/docs/v3/automatic-mode) , which means that after a tenant was identified, things like database connections, caches, filesystems, queues etc were switched to that tenant's context — his data completely isolated from the rest. In the current version, we're also making [manual tenancy](https://tenancyforlaravel.com/docs/v3/manual-mode) a first-class feature. We provide you with things like model traits if you wish to scope the data yourself. Tenant identification --------------------- For your application to be tenant-aware, a [tenant has to be identified](https://tenancyforlaravel.com/docs/v3/tenant-identification) . This package ships with many identification middleware classes. You may identify tenants by domain, subdomain, domain OR subdomain at the same time, path or request data. [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/introduction.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Compared to other packages | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Compared to other packages ========================== hyn/multi-tenancy ----------------- This package intends to provide you with the necessary tooling for adding multi-tenancy **manually** to your application. It will give you model traits, classes for creating tenant databases, and some additional tooling. It would have been a good option for when you want to implement multi-tenancy manually, but: * It isn't being actively developed — no features have been added in the past ~year * It makes testing a nightmare. Over the last few months, I've received this feedback: > But, I still can't run any tests in Hyn, and had some queuing problems I'm still nervous about. > > At the moment, our app is using latest Laravel and latest hyn/tenancy. The only thing I don't like about it is that tests are extremely fragile to the point where I don't dare mess with anything for risk of breaking everything > > By the way, this package is awesome! It's so much better than the hyn alternative which is a bit of a mess in my opinion... It is a pity that I did not come across it in the first place. I'm not sharing this to intentionally make hyn/multi-tenancy bad, but **be very careful if you decide to go with that package**. tenancy/tenancy --------------- This package intends to provide you with a framework for building your own multi-tenancy implementation. The documentation is quite lacking, so I couldn't get a too detailed idea of what it does, but from my understanding, it gives you things like events which you can use to build your own multi-tenancy logic. If you want the absolute highest flexibility and would otherwise build your own implementation, looking into this package might be useful. However, if you're looking for a package that will help you make a multi-tenant project quickly, this is probably not the right choice. spatie/laravel-multitenancy --------------------------- This package is a very simple implementation of multi-tenancy. It does the same thing as stancl/tenancy v2, but with far fewer features out of the box. The only benefit I see in this package compared to v2 of stancl/tenancy is that it uses Eloquent out of the box, which makes things like Cashier integration easier. But, that's irrelevant since we're in v3 already and v3 uses Eloquent. So, I suggest you consider this package only if you value simplicity for some reason, and aren't building anything with any amount of complexity and need for "business features". stancl/tenancy -------------- In my — biased, of course, but likely true as well — opinion, this package is the absolute best choice for the vast majority of applications. The only packages I'd realistically consider are mine (of course) and tenancy/tenancy if you need something **very** custom, though I don't see the reason for that in 99% of applications. This package attempts to be about as flexible as tenancy/tenancy, but also provide you with the absolute largest amount of out-of-the-box features and other tooling. It continues its path as the first package to have been using the automatic approach with adding many more features — most of which are "enterprise" features, in v3. To give you an incomplete-but-good-enough list of features, this package supports: * Multi-database tenancy * creating databases * MySQL * PostgreSQL * PostgreSQL (schema mode) * SQLite * creating database users * automatically switching the database * CLI commands, with more features than e.g. spatie/laravel-multitenancy * migrate * migrate:fresh * seed * Single-database tenancy * model traits with global scopes * Rich event system * **Very high testability** * Automatic tenancy * tenancy bootstrappers to switch: * database connections * redis connections * cache tags * filesystem roots * queue context * Manual tenancy * model traits * Out of the box tenant identification * domain identification * subdomain identification * path identification * request data identification * middleware classes for the methods above * CLI argument identification * manual identification (e.g. in tinker) * Integration with many packages * spatie/laravel-medialibrary * spatie/laravel-activitylog * Livewire * Laravel Nova * for managing tenants * for **using inside the tenant application** * Laravel Horizon * Laravel Telescope * Laravel Passport * **Syncing users (or any other database resources) between multiple tenant databases** * Dependency injection of the current tenant * Tenant **user impersonation** * **Cached tenant lookup**, universal for all tenant resolvers [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/package-comparison.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # The two applications | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) The two applications ==================== You will find these two terms a lot throughout this documentation: * central application * tenant application Those terms refer to the parts of your application that house the central logic, and the tenant logic. The tenant application is executed in tenant context — usually with the tenant's database, cache, etc. The central application is executed **when there is no tenant**. The central application will house your signup page **where tenants are created**, your admin panel used to **manage your tenants**, etc. The tenant application will likely house the larger part of your application — the real service being used by your tenants. [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/the-two-applications.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Installation | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Installation ============ Laravel 9.0 or higher is needed. Require the package using composer: composer require stancl/tenancy Then run the following command: php artisan tenancy:install It will create: * migrations * a config file (`config/tenancy.php`), * a routes file (`routes/tenant.php`), * and a service provider file `app/Providers/TenancyServiceProvider.php` Then add the service provider to your `bootstrap/providers.php` file: return [\ App\Providers\AppServiceProvider::class,\ App\Providers\TenancyServiceProvider::class, // <-- here\ ]; And finally, if you want to use a different central database than the one defined by `DB_CONNECTION` in the file `.env`, name your central connection (in `config/database.php`) `central` — or however else you want, but make sure it's the same name as the `tenancy.central_connection` config. [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/installation.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Quickstart Tutorial | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Quickstart Tutorial =================== This tutorial focuses on getting you started with stancl/tenancy 3.x quickly. It implements multi-database tenancy & domain identification. If you need a different implementation, then **that's absolutely possible with this package** and it's very easy to refactor to a different implementation. We recommend following this tutorial just **to get things working** so that you can play with the package. Then if you need to, you can refactor the details of the multi-tenancy implementation (e.g. single-database tenancy, request data identification, etc). Installation ------------ First, require the package using composer: composer require stancl/tenancy Then, run the `tenancy:install` command: php artisan tenancy:install This will create a few files: migrations, config file, route file and a service provider. Let's run the migrations: php artisan migrate Register the service provider in `bootstrap/providers.php`: return [\ App\Providers\AppServiceProvider::class,\ App\Providers\TenancyServiceProvider::class, // <-- here\ ]; Creating a tenant model ----------------------- Now you need to create a Tenant model. The package comes with a default Tenant model that has many features, but it attempts to be mostly unopinionated and as such, we need to create a custom model to use domains & databases. Create the file `app/Models/Tenant.php` like this: \App\Models\Tenant::class, Events ------ The defaults will work out of the box here, but a short explanation will be useful. The `TenancyServiceProvider` file in your `app/Providers` directory maps tenancy events to listeners. By default, when a tenant is created, it runs a `JobPipeline` (a smart thing that's part of this package) which makes sure that the `CreateDatabase`, `MigrateDatabase` and optionally other jobs (e.g. `SeedDatabase`) are ran sequentially. In other words, it creates & migrates the tenant's database after he's created — and it does this in the correct order, because normal event-listener mapping would execute the listeners in some stupid order that would result in things like the database being migrated before it's created, or seeded before it's migrated. Central routes -------------- We'll make a small change to your existing route files. Specifically, we'll make sure that central routes are registered on central domains only: // routes/web.php, api.php or any other central route files you have foreach (config('tenancy.central_domains') as $domain) { Route::domain($domain)->group(function () { // your actual routes }); } Alternatively, to keep your route files more clean, you can use [this approach](https://github.com/archtechx/tenancy/pull/1180#issuecomment-2006098346) to register all of your routes in the `using` callback of the Application Builder. Central domains --------------- Now we need to actually specify the central domains. A central domain is a domain that serves your "central app" content, e.g. the landing page where tenants sign up. Open the `config/tenancy.php` file and add them in: 'central_domains' => [\ 'saas.test', // Add the ones that you use. I use this one with Laravel Valet.\ ], If you're using Laravel Sail, no changes are needed, default values are good to go: 'central_domains' => [\ '127.0.0.1',\ 'localhost',\ ], Tenant routes ------------- Your tenant routes will look like this by default: Route::middleware([\ 'web',\ InitializeTenancyByDomain::class,\ PreventAccessFromCentralDomains::class,\ ])->group(function () { Route::get('/', function () { return 'This is your multi-tenant application. The id of the current tenant is ' . tenant('id'); }); }); These routes will only be accessible on tenant (non-central) domains — the `PreventAccessFromCentralDomains` middleware enforces that. Let's make a small change to dump all the users in the database, so that we can actually see multi-tenancy working. Open the file `routes/tenant.php` and apply the modification below: Route::get('/', function () { dd(\App\Models\User::all()); return 'This is your multi-tenant application. The id of the current tenant is ' . tenant('id'); }); Migrations ---------- To have users in tenant databases, let's move the `users` table migration (the file `database/migrations/0001_01_01_000000_create_users_table.php` or similar) to `database/migrations/tenant`. This will prevent the table from being created in the central database, and it will be instead created in the tenant database when a tenant is created — thanks to our event setup. If you have any other migrations that are necessary for your application, move those migrations as well. Creating tenants ---------------- > If you're using Laravel Sail, please refer the [Laravel Sail integration guide](https://tenancyforlaravel.com/docs/v3/integrations/sail) > : For testing purposes, we'll create a tenant in `tinker` — no need to waste time creating controllers and views for now. $ php artisan tinker >>> $tenant1 = App\Models\Tenant::create(['id' => 'foo']); >>> $tenant1->domains()->create(['domain' => 'foo.localhost']); >>> >>> $tenant2 = App\Models\Tenant::create(['id' => 'bar']); >>> $tenant2->domains()->create(['domain' => 'bar.localhost']); Now we'll create a user inside each tenant's database: App\Models\Tenant::all()->runForEach(function () { App\Models\User::factory()->create(); }); Trying it out ------------- Now we visit `foo.localhost` in our browser, replace `localhost` with one of the values of `central_domains` in the file `config/tenancy.php` as modified previously. We should see a dump of the users table where we see some user. If we visit `bar.localhost`, we should see a different user. [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/quickstart.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Upgrading from 2.x | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Upgrading from 2.x ================== Note that most of the package's code is different now, which means that you will have to update all of the places where you interact with the package's classes. That said, automatic tenancy will still work the same way. * Move all `_tenancy_` prefixed keys in tenant storage to `tenancy_` prefixed keys Run this **with 2.x code** (but put application down first, this will break your app and require that you update to 3.x immediately after). Of course make a backup of the DB first. (Note: You may want to make this part of a migration) use Stancl\Tenancy\Tenant; use Illuminate\Support\Str; tenancy() ->all() ->each(function (Tenant $tenant) { $keys = array_keys($tenant->data); $keys = array_filter($keys, function ($key) { return Str::startsWith($key, '_tenancy_'); }); foreach ($keys as $key) { $value = $tenant->data[$key]; unset($tenant->data[$key]); $tenant->data[str_replace('_tenancy_', 'tenancy_', $key)] = $value; } $tenant->save(); }); * Create migration file with `php artisan make:migration upgrade_tenancy_package` dropPrimary(); }); Schema::table('domains', function (Blueprint $table) { $table->increments('id')->first(); $table->unique('domain'); $table->timestamps(); }); Schema::table('tenants', function (Blueprint $table) { $table->json('data')->nullable()->change(); $table->timestamps(); }); DB::table('domains')->update([\ 'created_at' => now(),\ 'updated_at' => now()\ ]); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('domains', function (Blueprint $table) { $table->dropColumn('id'); $table->dropUnique(['domain']); $table->dropTimestamps(); $table->primary('domain'); }); Schema::table('tenants', function (Blueprint $table) { $table->json('data')->nullable(false)->change(); $table->dropTimestamps(); }); } } * Replace your Http Kernel with a stock version (copy it from laravel/laravel: [https://github.com/laravel/laravel/blob/master/app/Http/Kernel.php](https://github.com/laravel/laravel/blob/master/app/Http/Kernel.php) ) and add back in any changes you made. The package now doesn't necessitate any Kernel changes, so remove all of the 2.x ones. * Delete config, publish it & the new files using `php artisan tenancy:install`. Register the new provider (see instructions on the [Installation](https://tenancyforlaravel.com/docs/v3/installation) page) * Create Tenant model, as instructed on the [Tenants](https://tenancyforlaravel.com/docs/v3/tenants) page * Update routes to use the correct middleware, see the [Routes](https://tenancyforlaravel.com/docs/v3/routes) page [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/upgrading.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Configuration | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Configuration ============= The package is highly configurable. This page covers what you can configure in the `config/tenancy.php` file, but note that many more things are configurable. Some things can be changed by extending classes (e.g. the `Tenant` model), and **many** things can be changed using static properties. These things will _usually_ be mentioned on the respective pages of the documentation, but not every time. For this reason, don't be afraid to dive into the package's source code — whenever the class you're using has a `public static` property, **it's intended to be configured**. Static properties ----------------- You can set static properties like this (example): \Stancl\Tenancy\Middleware\InitializeTenancyByDomain::$onFail = function () { return redirect('https://my-central-domain.com/'); }; A good place to put these calls is your `app/Providers/TenancyServiceProvider`'s `boot()` method. ### Tenant model `tenancy.tenant_model` This config specifies what `Tenant` model should be used by the package. There's a high chance you're using a custom model, as instructed to by the [Tenants](https://tenancyforlaravel.com/docs/v3/tenants) page, so be sure to change it in the config. ### Unique ID generator `tenancy.id_generator` The `Stancl\Tenancy\Database\Concerns\GeneratesIds` trait, which is applied on the default `Tenant` model, will generate a unique ID (uuid by default) if no tenant id is supplied. If you wish to use autoincrement ids instead of uuids: 1. set this config key to null, or create a custom tenant model that doesn't use this trait 2. update the `tenants` table migration to use an incrementing column type instead of `string` 3. update the `domains` table migration `tenant_id` column to the same type as `tenants` id ### Domain model `tenancy.domain_model` Similar to the Tenant model config. If you're using a custom model for domains, change it in this config. If you're not using domains (e.g. if you're using path or request data identification) at all, ignore this config key altogether. ### Central domains `tenancy.central_domains` The list of domains that host your [central app](https://tenancyforlaravel.com/docs/v3/the-two-applications) . This is used by (among other things): * the `PreventAccessFromCentralDomains` middleware, to prevent access from central domains to tenant routes, * the `InitializeTenancyBySubdomain` middleware, to check whether the current hostname is a subdomain on one of your central domains. ### Bootstrappers `tenancy.bootstrappers` This config array lets you enable, disable or add your own [tenancy bootstrappers](https://tenancyforlaravel.com/docs/v3/tenancy-bootstrappers) . ### Database > If you're using Laravel Sail, please refer the [Laravel Sail integration guide](https://tenancyforlaravel.com/docs/v3/integrations/sail) > . This section is relevant to the multi-database tenancy, specifically, to the `DatabaseTenancyBootstrapper` and logic that manages tenant databases. See this section in the config, it's documented with comments. ### Cache `tenancy.cache.*` This section is relevant to cache separation, specifically, to the `CacheTenancyBootstrapper`. Note: To use the cache separation, you need to use a cache store that supports tagging, which is usually Redis. See this section in the config, it's documented with comments. ### Filesystem `tenancy.filesystem.*` This section is relevant to storage separation, specifically, to the `FilesystemTenancyBootstrapper`. See this section in the config, it's documented with comments. ### Redis `tenancy.redis.*` This section is relevant to Redis data separation, specifically, to the `RedisTenancyBootstrapper`. Note: To use this bootstrapper, you need phpredis. See this section in the config, it's documented with comments. ### Features `tenancy.features` This config array lets you enable, disable or add your own [feature classes](https://tenancyforlaravel.com/docs/v3/optional-features) . ### Migration parameters `tenancy.migration_parameters` This config array lets you set parameters used by default when running the `tenants:migrate` command (or when this command is executed using the `MigrateDatabase` job). Of course, all of these parameters can be overridden by passing them directly in the command call, be it in CLI or using `Artisan::call()`. ### Seeder parameters `tenancy.seeder_parameters` The same as migration parameters, but for `tenants:seed` and the `SeedDatabase` job. [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/configuration.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Domains | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Domains ======= Note: Domains are optional. If you're using path or request data identification, you don't need to worry about them. To add a domain to a tenant, use the `domains` relationship: $tenant->domains()->create([\ 'domain' => 'acme',\ ]); If you use the subdomain identification middleware, the example above will work for `acme.{any of your central domains}`. If you use the domain identification middleware, use the full hostname like `acme.com`. If you use the combined domain/subdomain identification middleware, you should use both `acme` as a subdomain and `acme.com` for the domain. Note that starting with Laravel 8 and up, Laravel TrustHost middleware is enabled by default (see [laravel/laravel#5477](https://github.com/laravel/laravel/pull/5477) ). This blocks Domain-based tenant identification since these requests will be treated as 'untrusted' unless added as a trusted host. You can either comment out this middleware in your App\\Http\\Kernel.php, or you can add the custom tenant domains in the App\\Http\\Middleware\\TrustHosts.php `hosts()` method. To retrieve the current domain model (when using domain identification), you may access the `$currentDomain` public static property on `DomainTenantResolver`. Local development ----------------- For local development, you may use `*.localhost` domains (like `foo.localhost`) for tenants. On many operating systems, these work the same way as `localhost`. If you're using Valet, you may want to use e.g. `saas.test` for the central domain and `foo.saas.test`, `bar.saas.test` etc for tenant domains. Alternatively, if you want to use **multiple second-level domains**, you can use the `valet link` command to attach additional domains to the project. For example: `valet link bar.test` [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/domains.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Tenants | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Tenants ======= A tenant can be any model that implements the `Stancl\Tenancy\Contracts\Tenant` interface. The package comes with a base `Tenant` model that's ready for common things, though will require extending in most cases as it attempts not to be too opinionated. The base model has the following features on top of the ones that are necessary by the interface: * Forced central connection (lets you interact with `Tenant` models even in the tenant context) * Data column trait — lets you store arbitrary keys. Attributes that don't exist as columns on your `tenants` table go to the `data` column as serialized JSON. * Id generation trait — when you don't supply an ID, a random uuid will be generated. An alternative to this would be using AUTOINCREMENT columns. If you wish to use numerical ids, change the `create_tenants_table` migration to use `bigIncrements()` or some such column type, and set `tenancy.id_generator` config to null. That will disable the ID generation altogether, falling back to the database's autoincrement mechanism. Tenant Model ------------ **Most** applications using this package will want domain/subdomain identification and tenant databases. To do this, create a new model, e.g. `App\Tenant`, that looks like this: \App\Tenant::class, If you want to customize the `Domain` model, you can do that too. **If you don't need domains or databases, ignore the steps above.** Everything will work just as well. Creating tenants ---------------- You can create tenants like any other models: $tenant = Tenant::create([\ 'plan' => 'free',\ ]); After the tenant is created, an event will be fired. This will result in things like the database being created and migrated, depending on what jobs listen to the event. Custom columns -------------- Attributes of the tenant model which don't have their own column will be stored in the `data` JSON column. You can set these attributes like you'd set normal model attributes: $tenant->update([\ 'attributeThatHasNoColumn' => 'value', // stored in the `data` JSON column\ 'plan' => 'free' // stored in the dedicated `plan` column (see below)\ ]); or $tenant->customAttribute = 'value'; // stored in the `data` JSON column $tenant->plan = 'free'; // stored in the `plan` column (see below) $tenant->save(); You may define the custom columns (that **won't** be stored in the `data` JSON column) by overriding the `getCustomColumns()` method on your `Tenant` model: public static function getCustomColumns(): array { return [\ 'id',\ 'plan',\ ]; } **Don't forget to keep `id` in the custom columns!** If you want to rename the `data` column, rename it in a migration and implement this method on your model: public static function getDataColumn(): string { return 'my-data-column'; } Note that querying data inside the `data` column with `where()` will require that you do for example: where('data->foo', 'bar') The data column is encoded/decoded only on model retrieval and saving. Also a good rule of thumb is that when you need to query the data with `WHERE` clauses, it should have a dedicated column. This will improve performance and you won't have to think about the `data->` prefixing. Running commands in the tenant context -------------------------------------- You may run commands in a tenant's context and then return to the previous context (be it central, or another tenant's) by passing a callable to the `run()` method on the tenant object. For example: $tenant->run(function () { User::create(...); }); Internal keys ------------- Keys that start with the internal prefix (`tenancy_` by default, but you can customize this by overriding the `internalPrefix()` method) are for internal use, so don't start any attribute/column names with that. Events ------ The `Tenant` model dispatches Eloquent events, all of which have their own respective class. You can read more about this on the [Event system](https://tenancyforlaravel.com/docs/v3/event-system) page. Accessing the current tenant ---------------------------- You may access the current tenant using the `tenant()` helper. You can also pass an argument to get an attribute from that tenant model, e.g. `tenant('id')`. Alternatively, you may typehint the `Stancl\Tenancy\Contracts\Tenant` interface to inject the model using the service container. Incrementing IDs ---------------- By default, the migration uses `string` for the `id` column, and the model generates UUIDs when you don't supply an `id` during tenant creation. If you'd like to use incrementing ids instead, you can override the `getIncrementing()` method: public function getIncrementing() { return true; } [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/tenants.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Routes | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Routes ====== This package has a concept of central routes and tenant routes. Central routes are only available on central domains, and tenant routes are only available on tenant domains. If you don't use domain identification, then all routes are always available and you may skip the details about preventing access from other domains. Central routes -------------- You may register central routes in `routes/web.php` or `routes/api.php` like you're used to. However, you need to make one small change to your RouteServiceProvider. > Note: Since Laravel 11, there isn't a `RouteServiceProvider`, so you can instead wrap your central routes in a `Route::domain(...)->group(...)` call. See the quickstart guide for an example. You don't want central routes — think landing pages and sign up forms — to be accessible on tenant domains. For that reason, register them in such a way that they're **only** accessible on your central domains: // RouteServiceProvider protected function mapWebRoutes() { foreach ($this->centralDomains() as $domain) { Route::middleware('web') ->domain($domain) ->namespace($this->namespace) ->group(base_path('routes/web.php')); } } protected function mapApiRoutes() { foreach ($this->centralDomains() as $domain) { Route::prefix('api') ->domain($domain) ->middleware('api') ->namespace($this->namespace) ->group(base_path('routes/api.php')); } } protected function centralDomains(): array { return config('tenancy.central_domains', []); } Note: If you're using multiple central domains, you can't use route names, because different routes (= different combinations of domains & paths) can't share the same name. If you need to use a different central domain for testing, use `config()->set()` in your TestCase `setUp()`. Tenant routes ------------- You may register tenant routes in `routes/tenant.php`. These routes have no middleware applied on them, and their controller namespace is specified in `app/Providers/TenancyServiceProvider`. By default, you will see the following setup: Route::middleware([\ 'web',\ InitializeTenancyByDomain::class,\ PreventAccessFromCentralDomains::class,\ ])->group(function () { Route::get('/', function () { return 'This is your multi-tenant application. The id of the current tenant is ' . tenant('id'); }); }); Routes within this group will have the `web` middleware group, an initialization middleware, and finally, a middleware related to the section below applied on them. You may do the same for the `api` route group, for instance. Also, you may use different initialization middleware than the domain one. For a full list, see the [Tenant identification](https://tenancyforlaravel.com/docs/v3/tenant-identification) page. ### Conflicting paths Due to the order in which the service providers (and as such, their routes) are registered, tenant routes will take precedence over central routes. So if you have a `/` route in your `routes/web.php` file but also `routes/tenant.php`, the tenant route will be used on tenant domains. However, tenant routes that don't have their central counterpart will still be accessible on central domains and will result in a "Tenant could not be identified on domain ..." error. To avoid this, use the `Stancl\Tenancy\Middleware\PreventAccessFromCentralDomains` middleware on all of your tenant routes. This middleware will abort with a 404 if the user is trying to visit a tenant route on a central domain. Universal routes ---------------- See the [Universal Routes feature](https://tenancyforlaravel.com/docs/v3/features/universal-routes) . [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/routes.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Event system | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Event system ============ This package is heavily based around events, which makes it incredibly flexible. By default, the events are configured in such a way that the package works like this: * A request comes in for a tenant route and hits an identification middleware * The identification middleware finds the correct tenant and runs $this->tenancy->initialize($tenant); * The `Stancl\Tenancy\Tenancy` class sets the `$tenant` as the current tenant and fires a `TenancyInitialized` event * The `BootstrapTenancy` class catches the event and executes classes known as [tenancy bootstrappers](https://tenancyforlaravel.com/docs/v3/tenancy-bootstrappers) . * The tenancy bootstrappers make changes to the application to make it "scoped" to the current tenant. This by default includes: * Switching the database connection * Replacing `CacheManager` with a scoped cache manager * Suffixing filesystem paths * Making queues store the tenant id & initialize tenancy when being processed Again, all of the above is configurable. You might even disable all tenancy bootstrappers, and just use tenant identification and scope your app manually around the tenant stored in `Stancl\Tenancy\Tenancy`. The choice is yours. TenancyServiceProvider ====================== This package comes with a very convenient service provider that's added to your application when you install the package. This service provider is used for mapping listeners to events specific to the package and is the place where you should put any tenancy-specific service container calls — to not pollute your AppServiceProvider. Note that you can register listeners to this package's events **anywhere you want**. The event/listener mapping in the service provider exists only to make your life easier. If you want to register the listeners manually, like in the example below, you can. Event::listen(TenancyInitialized::class, BootstrapTenancy::class); Bootstrapping tenancy ===================== By default, the `BootstrapTenancy` class is listening to the `TenancyInitialized` event (exactly as you can see in the example above). That listener will execute the configured tenancy bootstrappers to transition the application into the tenant's context. You can read more about this on the [tenancy bootstrappers](https://tenancyforlaravel.com/docs/v3/tenancy-bootstrappers) page. Conversely, when the `TenancyEnded` event fires, the `RevertToCentralContext` event transitions the app back into the central context. Job pipelines ============= You may want to use job pipelines even in projects that don't use this package — I think they're a cool concept so they're extracted into a separate package: [github.com/stancl/jobpipeline](https://github.com/stancl/jobpipeline) The `JobPipeline` is a simple, yet **extremely powerful** class that lets you **convert any (series of) jobs into event listeners.** You may use a job pipeline like any other listener, so you can register it in the `TenancyServiceProvider`, `EventServiceProvider` using the `$listen` array, or in any other place using `Event::listen()` — up to you. Creating job pipelines ---------------------- To create a job pipeline, start by specifying the jobs you want to use: use Stancl\JobPipeline\JobPipeline; use Stancl\Tenancy\Jobs\{CreateDatabase, MigrateDatabase, SeedDatabase}; JobPipeline::make([\ CreateDatabase::class,\ MigrateDatabase::class,\ SeedDatabase::class,\ ]) Then, specify what variable you want to pass to the jobs. This will usually come from the event. use Stancl\JobPipeline\JobPipeline; use Stancl\Tenancy\Jobs\{CreateDatabase, MigrateDatabase, SeedDatabase}; use Stancl\Tenancy\Events\TenantCreated; JobPipeline::make([\ CreateDatabase::class,\ MigrateDatabase::class,\ SeedDatabase::class,\ ])->send(function (TenantCreated $event) { return $event->tenant; }) Next, decide if you want to queue the pipeline. By default, pipelines are synchronous (= not queued). If you **do** want pipelines to be queued by default, you can do that by setting a static property: `\Stancl\JobPipeline\JobPipeline::$shouldBeQueuedByDefault = true;` use Stancl\Tenancy\Events\TenantCreated; use Stancl\JobPipeline\JobPipeline; use Stancl\Tenancy\Jobs\{CreateDatabase, MigrateDatabase, SeedDatabase}; JobPipeline::make([\ CreateDatabase::class,\ MigrateDatabase::class,\ SeedDatabase::class,\ ])->send(function (TenantCreated $event) { return $event->tenant; })->shouldBeQueued(true), Finally, convert the pipeline to a listener and bind it to an event: use Stancl\Tenancy\Events\TenantCreated; use Stancl\JobPipeline\JobPipeline; use Stancl\Tenancy\Jobs\{CreateDatabase, MigrateDatabase, SeedDatabase}; use Illuminate\Support\Facades\Event; Event::listen(TenantCreated::class, JobPipeline::make([\ CreateDatabase::class,\ MigrateDatabase::class,\ SeedDatabase::class,\ ])->send(function (TenantCreated $event) { return $event->tenant; })->shouldBeQueued(true)->toListener()); Note that you can use job pipelines even for converting single jobs to event listeners. That's useful if you have some logic in job classes and don't want to create listener classes just to be able to run these jobs as a result of an event being fired. Available events ================ Note: Some database events (`DatabaseMigrated`, `DatabaseSeeded`, `DatabaseRolledback` and possibly others) are **fired in the tenant context.** Depending on how your application bootstraps tenancy, you might need to be specific about interacting with the central database in these events' listeners — that is, if you need to. Note: All events are located in the `Stancl\Tenancy\Events` namespace. ### **Tenancy** * `InitializingTenancy` * `TenancyInitialized` * `EndingTenancy` * `TenancyEnded` * `BootstrappingTenancy` * `TenancyBootstrapped` * `RevertingToCentralContext` * `RevertedToCentralContext` Note the difference between _initializing tenancy and bootstrapping_ tenancy. Tenancy is initialized when a tenant is loaded into the `Tenancy` object. Whereas bootstrapping happens **as a result of initialization** — if you're using automatic tenancy, the `BootstrapTenancy` class is listening to the `TenancyInitialized` event and after it's done executing bootstrappers, it fires an event saying that tenancy was bootstrapped. You want to use the bootstrapped event if you want to execute something **after the app has been transitioned to the tenant context.** ### Tenant The following events are dispatched as a result of Eloquent events being fired in the default `Tenant` implementation (the most often used events are bold): * `CreatingTenant` * **`TenantCreated`** * `SavingTenant` * `TenantSaved` * `UpdatingTenant` * `TenantUpdated` * `DeletingTenant` * **`TenantDeleted`** ### Domain These events are optional. They're only relevant to you if you're using domains for your tenants. * `CreatingDomain` * **`DomainCreated`** * `SavingDomain` * `DomainSaved` * `UpdatingDomain` * `DomainUpdated` * `DeletingDomain` * **`DomainDeleted`** ### Database These events are also optional. They're relevant to you if you're using multi-database tenancy: * `CreatingDatabase` * **`DatabaseCreated`** * `MigratingDatabase` * `DatabaseMigrated` * `SeedingDatabase` * `DatabaseSeeded` * `RollingBackDatabase` * `DatabaseRolledBack` * `DeletingDatabase` * **`DatabaseDeleted`** ### Resource syncing * **`SyncedResourceSaved`** * `SyncedResourceChangedInForeignDatabase` [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/event-system.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Tenancy bootstrappers | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Tenancy bootstrappers ===================== Tenancy bootstrappers are classes which make your application tenant-aware in such a way that you don't have to change a line of your code, yet things will be scoped to the current tenant. The package comes with these bootstrappers out of the box: Database tenancy bootstrapper ----------------------------- The database tenancy bootstrapper switches the **default** database connection to `tenant` after it constructs the connection for that tenant. [Customizing databases](https://tenancyforlaravel.com/docs/v3/customizing-databases) Note that only the **default** connection is switched. If you use another connection explicitly, be it using `DB::connection('...')`, a model `getConnectionName()` method, or a model trait like `CentralConnection`, **it will be respected.** The bootstrapper doesn't **force** any connections, it merely switches the default one. Cache tenancy bootstrapper -------------------------- The cache tenancy bootstrapper replaces the Laravel's CacheManager instance with a custom CacheManager that adds tags with the current tenant's ids to each cache call. This scopes cache calls and lets you selectively clear tenants' caches: php artisan cache:clear --tag=tenant_123 Note that you must use a cache store that supports tagging, e.g. Redis. Filesystem tenancy bootstrapper ------------------------------- The filesystem bootstrapper makes your app's `Storage` facade and the `storage_path()` and `asset()` helpers tenant-aware by modifying the paths they return. > Note: If you want to bootstrap filesystem tenancy differently (e.g. provision an S3 bucket for each tenant), you can absolutely do that. Take a look at the package's bootstrappers to get an idea of how to write one yourself, and feel free to implement it any way you want. ### Storage path helper The bootstrapper suffixes the path returned by `storage_path()` to make the helper tenant-aware. * The suffix is built by appending the tenant key to your `suffix_base`. The `suffix_base` is `tenant` by default, but feel free to change it in the `tenancy.filesystem` config. For example, the suffix will be `tenant42` if the tenant's key is `42` and the `suffix_base` is `tenant`. * After the suffixing, `storage_path()` helper returns `"/$path_to_your_application/storage/tenant42/"` Since `storage_path()` will be suffixed, your folder structure will look like this: ![The folder structure](/assets/images/file_structure_tenancy.png) Logs will be saved in `storage/logs` regardless of any changes to `storage_path()` and regardless of the tenant. ### Storage facade The bootstrapper also makes the `Storage` facade tenant-aware by suffixing the roots of disks listed in `config('tenancy.filesystem.disks')` and by overriding the disk roots in `config('tenancy.filesystem.root_override')` (disk root = the disk path used by the `Storage` facade). The root of each disk listed in `config('tenancy.filesystem.disks')` will be suffixed. Doing that alone could cause unwanted behavior since Laravel does its own suffixing, so the filesystem config has the `root_override` section, which lets you override the disk roots **after** tenancy has been initialized: // Tenancy config (tenancy.filesystem.root_override) // %storage_path% gets replaced by storage_path()'s output // E.g. Storage::disk('local')->path('') will return "/$path_to_your_application/storage/tenant42/app" // (Given a suffix_base of 'tenant' and a tenant with a key of `42`. Same as in the example above in the Storage path helper section) 'root_override' => [\ 'local' => '%storage_path%/app/',\ 'public' => '%storage_path%/app/public/',\ ], To make the tenant-aware `Storage` facade work with a custom disk, add the disk's name to `config('tenancy.filesystem.disks')` and if the disk is local, override its root in `config('tenancy.filesystem.root_override')` as shown above. With S3, overriding the disk roots is not necessary – `Storage::disk('s3')->path('foo.txt')` will return `/tenant42/foo.txt`. ### Assets The filesystem bootstrapper makes the `asset()` helper link to the files _of the current tenant_. By default, the bootstrapper makes the helper output a URL pointing to the TenantAssetsController (`/tenancy/assets/...`), which returns a file response: // TenantAssetsController return response()->file(storage_path('app/public/' . $path)); The package expects the assets to be stored in your tenant's `app/public/` directory. For global assets (non-private assets shared among all tenants), you may want to create a disk and use URLs from that disk instead. For example: Storage::disk('branding')->url('header-logo.png'); To access global assets such as JS/CSS assets, you can use `global_asset()` and `mix()`. Configuring the asset URL (`ASSET_URL` in your `.env`) changes the `asset()` helper's behavior – when the asset URL is set, the bootstrapper will suffix the configured asset URL (the same way `storage_path()` gets suffixed), and make the `asset()` helper output that instead of a path to the TenantAssetsController. You can disable tenancy of `asset()` in the config (`tenancy.filesystem.asset_helper_tenancy`) and explicitly use `tenant_asset()` instead. `tenant_asset()` **always** returns a path to the TenantAssetController: `tenant_asset('foo.txt')` returns `your-site.com/tenancy/assets/foo.txt`. You may want to do that if you're facing issues using a package that utilizes `asset()` inside the tenant app. Before using the `asset()` helper, make sure to [assign the identification middleware you're using in your app to TenantAssetsController's `$tenancyMiddleware`](https://tenancyforlaravel.com/docs/v3/configuration#static-properties) : // TenancyServiceProvider (don't forget to import the classes) public function boot() { // Update the middleware used by the asset controller TenantAssetsController::$tenancyMiddleware = InitializeTenancyByDomainOrSubdomain::class; } Queue tenancy bootstrapper -------------------------- This bootstrapper adds the current tenant's ID to the queued job payloads, and initializes tenancy based on this ID when jobs are being processed. The bootstrapper has a static `$forceRefresh` property which is `false` by default. Setting the property to `true` will make tenancy re-initialize for each queued job. This is useful when you're changing the tenant's state (e.g. properties in the `data` column) and want the next job to initialize tenancy again with the new data. Features like the Tenant Config are only executed when tenancy is initialized, so the re-initialization is needed in some cases. You can read more about this on the _Queues_ page: [Queues](https://tenancyforlaravel.com/docs/v3/queues) Redis tenancy bootstrapper -------------------------- If you're using `Redis` calls (not cache calls, **direct** Redis calls) inside the tenant app, you will want to scope Redis data too. To do this, use this bootstrapper. It changes the Redis prefix for each tenant. Note that you need phpredis, predis won't work. Writing custom bootstrappers ---------------------------- If you want to bootstrap tenancy for something not covered by this package — or something covered by this package, but you want different behavior — you can do that by creating a bootstrapper class. The class must implement the `Stancl\Tenancy\Contracts\TenancyBootstrapper` interface: namespace App; use Stancl\Tenancy\Contracts\TenancyBootstrapper; use Stancl\Tenancy\Contracts\Tenant; class MyBootstrapper implements TenancyBootstrapper { public function bootstrap(Tenant $tenant) { // ... } public function revert() { // ... } } Then, register it in the `tenancy.bootstrappers` config: 'bootstrappers' => [\ Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper::class,\ Stancl\Tenancy\Bootstrappers\CacheTenancyBootstrapper::class,\ Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper::class,\ Stancl\Tenancy\Bootstrappers\QueueTenancyBootstrapper::class,\ \ App\MyBootstrapper::class,\ ], [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/tenancy-bootstrappers.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Optional features | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Optional features ================= "Features" are classes that provide additional functionality that's not needed for the core tenancy logic. Out of the box, the package comes with these Features: * [`UserImpersonation`](https://tenancyforlaravel.com/docs/v3/features/user-impersonation) for generating impersonation tokens for users of a tenant's DB from other contexts * [`TelescopeTags`](https://tenancyforlaravel.com/docs/v3/features/telescope-tags) for adding tags with the current tenant id to Telescope entries * [`TenantConfig`](https://tenancyforlaravel.com/docs/v3/features/tenant-config) for mapping keys from the tenant storage into the application config * [`CrossDomainRedirect`](https://tenancyforlaravel.com/docs/v3/features/cross-domain-redirect) for adding a `domain()` macro on `RedirectResponse` letting you change the intended hostname of the generated route * [`UniversalRoutes`](https://tenancyforlaravel.com/docs/v3/features/universal-routes) for route actions that work in both the central & tenant context * [`ViteBundler`](https://tenancyforlaravel.com/docs/v3/features/vite-bundler) for making Vite generate the correct asset paths All of the package's Features are in the `Stancl\Tenancy\Features` namespace. You may register features by adding their class names to the `tenancy.features` config. [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/optional-features.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Automatic tenancy mode | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Automatic mode ============== By default, the package bootstraps tenancy automatically in the background. This means that when a tenant is identified (usually using middleware), the default database/cache/filesystem/etc is switched to that tenant's context. You can read more about this on the [Tenancy bootstrappers](https://tenancyforlaravel.com/docs/v3/tenancy-bootstrappers) page. The flow goes like this: `TenancyInitialized` fired → `BootstrapTenancy` listens → executes tenancy bootstrappers It's recommended to use this mode, because: * Separation of concerns. Tenancy happens one layer below your application. If you need to change the details of how tenancy is bootstrapped, you can do that without having to change a ton of your app code. * You don't have to think about the internals of how tenancy works when writing your application code. When you're writing the tenant part of the application, you're simply writing e.g. an e-commerce application, not a multi-tenant e-commerce application. No need to think about database connections when writing validation rules. * Great integration with other packages. Switching the default database connection (and other things) is the only way to integrate many packages into the tenant part of the application. For example, you can use Laravel Nova to manage resources inside the tenant application. [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/automatic-mode.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Tenant-specific config | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Tenant config ============= It's likely you will need to use tenant-specific config in your application. That config could be API keys, things like "products per page" and many other things. You could just use the tenant model to get these values, but you may still want to use Laravel's `config()` because of: * separation of concerns — if you just write tenancy implementation-agnostic `config('shop.products_per_page')`, you will have a much better time changing tenancy implementations * default values — you may want to use the tenant storage only to override values in your config file **Enabling the feature** ------------------------ Uncomment the following line in your `tenancy.features` config: // Stancl\Tenancy\Features\TenantConfig::class, **Configuring the mapping** --------------------------- This feature maps keys in the tenant storage (properties on the `Tenant` model, these may be [custom columns](https://tenancyforlaravel.com/docs/v3/tenants#custom-columns) or virtual columns within `data`) to config keys based on the `$storageToConfigMap` [static property](https://tenancyforlaravel.com/docs/v3/configuration#static-properties) . For example, if your `$storageToConfigMap` looked like this: \Stancl\Tenancy\Features\TenantConfig::$storageToConfigMap = [\ 'paypal_api_key' => 'services.paypal.api_key',\ ]; the value of `paypal_api_key` in tenant model would be copied to the `services.paypal.api_key` config when tenancy is initialized. Mapping the value to multiple config keys ----------------------------------------- Sometimes you may want to copy the value to multiple config keys. To do that, specify the config keys as an array: \Stancl\Tenancy\Features\TenantConfig::$storageToConfigMap = [\ 'locale' => [\ 'app.locale',\ 'locales.default',\ ],\ ]; [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/features/tenant-config.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Telescope tags | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Telescope tags ============== [Laravel Telescope](https://github.com/laravel/telescope) provides insight into the requests coming into your application. You can filter those requests by tag. You can automatically tag all requests by the active tenant by enabling the Telescope tag feature. **Enabling the feature** ------------------------ Uncomment the following line in your `tenancy.features` config: // Stancl\Tenancy\Features\TelescopeTags::class, [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/features/telescope-tags.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Migrations | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Migrations ========== Move your tenant migrations to the `database/migrations/tenant` directory. You can execute them using `php artisan tenants:migrate`. Note that all migrations share the same PHP namespace, so even if you use the same table name in the central and tenant databases, you have to use different migration (class) names. If you use a modular approach to developing your project, you may have tenant migrations in multiple places. Luckily, you can specify where the package should look for tenant migrations. The `database/migrations/tenant` directory is just the default. To set these paths, go to your `config/tenancy.php` file and change the value of the [`--paths` parameter in `migration_parameters`](https://github.com/stancl/tenancy/blob/f2a3cf028ce68e2d55c26751af0bd5a447269894/assets/config.php#L177) . You may specify any number of paths in that array. [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/migrations.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Universal routes | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Universal Routes ================ > Note: If you need custom [onFail](https://tenancyforlaravel.com/docs/v3/tenant-identification) > logic, you cannot use this feature as it will override any of your changes to that logic. Instead, look into the source code of this feature and make your onFail logic implement universal routes too. If you do this, make sure to disable this feature in the config file followed by a cache clear. Sometimes, you may want to use the exact same **route action** both in the central application and the tenant application. Note the emphasis on route **action** — you may use the same **path** with different actions in central & tenant routes, whereas this section covers using the same **route and action**. Generally, try to avoid these use cases as much as possible and prefer duplicating the code. Using the same controller and model for users in central & tenant apps will break down once you need slightly different behavior — e.g. different views returned by controllers, different behavior in models, etc. First, enable the `UniversalRoutes` feature by uncommenting the following line in your `tenancy.features` config: Stancl\Tenancy\Features\UniversalRoutes::class, Next, go to your `bootstrap/app.php` file and add the following middleware group: ->withMiddleware(function (Middleware $middleware) { $middleware->group('universal', []); }) We will use this middleware group as a "flag" on the route, to mark it as a universal route. We don't need any actual middleware inside the group. Then, create a route like this: Route::get('/foo', function () { // ... })->middleware(['universal', InitializeTenancyByDomain::class]); And the route will work in both central and tenant applications. Should a tenant be found, tenancy will be initialized. Otherwise, the central context will be used. If you're using a different middleware, look at the `UniversalRoutes` feature source code and change the public static property accordingly. [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/features/universal-routes.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Early identification | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Early identification ==================== A slight "gotcha" with using the automatic approach to transition the application's context based on a route middleware is that **route-level middleware is executed after controller constructors.** The implication of this is if you're using dependency injection to inject some services in the controller constructors, **they will read from the central context**, because route-level middleware hasn't initialized tenancy yet. There are two ways to solve it, the former of which is preferable. Not using constructor DI ------------------------ You can inject dependencies in route **actions**, meaning: If you have a route that binds a `Post` model, you can still inject dependencies like this: // Note that this is sort-of pseudocode. Notice the route action DI // and just skim the rest :) Route::get('/post/{post}/edit', 'PostController@edit'); class PostController { public function edit(Request $request, Post $post, Cloudinary $cloudinary) { if ($request->has('image')) { $post->image_url = $cloudinary->store($request->file('image')); } } } If you don't like that because you access some dependency from many actions, consider creating a memoized method: class PostController { protected Cloudinary $cloudinary; protected cloudinary(): Cloudinary { // If you don't like the service location here, injecting Application // in the controller constructor is one thing that's 100% safe. return $this->cloudinary ??= app(Cloudinary::class); } public function edit(Request $request, Post $post) { if ($request->has('image')) { $post->image_url = $this->cloudinary()->store( $request->file('image') ); } } } Using a more complex middleware setup ------------------------------------- > Note: There's a new MW in v3 for preventing access from central domains. v2 was doing this a bit differently. The manual for implementing this will come soon, for now you can look at how 2.x does this. In short: The `InitializeTenancy` mw is part of the global middleware stack, which doesn't have access to route information, but is executed prior to controller constructors. The `PreventAccessFromTenantDomains` mw checks that we're vising a tenant route on a tenant domain, or a central route on a central domain — and if not, it aborts the request, either by 404 or by redirecting us to a home url on the tenant domain. Here's the logic visually: ![Early identification middleware](/assets/images/stancl_tenancy_middleware.png) And here are the relevant files in 2.x codebase: * [https://github.com/stancl/tenancy/blob/2.x/src/Middleware/InitializeTenancy.php](https://github.com/stancl/tenancy/blob/2.x/src/Middleware/InitializeTenancy.php) * [https://github.com/stancl/tenancy/blob/2.x/src/Middleware/PreventAccessFromTenantDomains.php](https://github.com/stancl/tenancy/blob/2.x/src/Middleware/PreventAccessFromTenantDomains.php) [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/early-identification.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Cross-domain redirect | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Cross-domain redirect ===================== To enable this feature, uncomment the `Stancl\Tenancy\Features\CrossDomainRedirect::class` line in your `tenancy.features` config. Sometimes you may want to redirect the user to a specific route on a different domain (than the current one). Let's say you want to redirect a tenant to the `home` path on their domain after they sign up: return redirect()->route('home')->domain($domain); You can also use the `tenant_route()` helper to redirect users to another domain. return redirect(tenant_route($domain, 'home')); [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/features/cross-domain-redirect.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Vite bundler | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Vite bundler ============ Enabling the `ViteBundler` feature makes Vite generate correct asset paths by using the `global_asset()` helper instead of the default `asset()` helper. To enable the feature, uncomment `Stancl\Tenancy\Features\ViteBundler::class` in the `features` section of the tenancy config: 'features' => [\ // [...]\ Stancl\Tenancy\Features\ViteBundler::class,\ ], [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/features/vite-bundler.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Multi-database tenancy | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Multi-database tenancy ====================== The package comes with all the tooling necessary for multi-database tenancy. TenantDatabaseManagers ---------------------- TenantDatabaseManagers are classes which manage tenant databases — they primarily take care of creating and deleting them. There are database managers for all Laravel-supported DB drivers (MySQL, PostgreSQL, SQLite). There's also a database manager for using a single database, but multiple schemas (one per tenant) with PostgreSQL. See the `database` section of the tenancy config for more details. Commands -------- There are also commands for working with tenant databases. Namely, `tenants:migrate` and `tenants:seed`. See the [console commands page](https://tenancyforlaravel.com/docs/v3/console-commands) of the documentation. Jobs & Listeners ---------------- By default, when a tenant is created, there's also a database created for him. This is done using a JobPipeline listener in the `TenancyServiceProvider`. See the [event system page](https://tenancyforlaravel.com/docs/v3/event-system) of the documentation. [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/multi-database-tenancy.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Customizing tenant databases | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Customizing databases ===================== You may customize how a tenant's DB connection is constructed by storing specific internal keys on the tenant. If you changed the internal prefix on the tenant model, then use that instead of `tenancy_`. Specifying database names ------------------------- You may specify the tenant's database name by setting the `tenancy_db_name` key when creating the tenant. Tenant::create([\ 'tenancy_db_name' => 'acme',\ ]); When you don't specify the tenant's database name, it's constructed using: `tenancy.database.prefix` config + tenant id + `tenancy.database.suffix` config Therefore, another way to specify database names is to set the tenant id during creation, rather than letting it be randomly generated: Tenant::create([\ 'id' => 'acme',\ ]); Specifying database credentials ------------------------------- Database user & password are only created when you use the permission controlled MySQL database manager. See the database config for more info. You may specify the username and password for the user that will be created along with the tenant database. Tenant::create([\ 'tenancy_db_username' => 'foo',\ 'tenancy_db_password' => 'bar',\ ]); The user will be given the grants specified in the `PermissionControlledMySQLDatabaseManager::$grants` array. Feel free to customize this by setting it to a different value like any other public static property. Note that you don't want to grant the users the ability to grant themselves more grants. Specifying template connections ------------------------------- > **Important:** there should be no `tenant` connection in `config/database.php`. If you create a template connection for tenants, name it something like `tenant_template`. The `tenant` connection is entirely managed by the package and gets reset to `null` when tenancy is ended. To specify the connection that should be used to construct this tenant's database connection (the array like you'd find in `config/database.php`), set the `tenancy_db_connection` key. Otherwise, the connection whose name is in the `tenancy.database.template_connection` config will be used. If that key is null, the central connection will be used. Specifying other connection details ----------------------------------- You may also set specific connection details without necessarily creating a new connection. The final "connection array" will be constructed by merging the following: * the template connection * the database name * optionally, the username and password * all `tenancy_db_*` keys This means that you can store a value for e.g. `tenancy_db_charset` if you want to specify the charset for the tenant's database connection for whatever reason. [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/customizing-databases.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Session scoping | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Session scoping =============== Session scoping is one thing that you might have to deal with yourself. The issue occurs when you're using multiple tenant databases. Users can change their session cookie's domain and their session data will be shared in another tenant's application. Here's how you can prevent this. Storing sessions in the database -------------------------------- > Note: This approach has more variables than Redis, making it less reliable. It's recommended to use phpredis + the Redis session driver for proper session scoping. Since the databases are automatically separated, simply using the database as the session driver will make this problem disappear altogether. Storing sessions in Redis ------------------------- This is the same solution as using the DB session driver. If you use the [`RedisTenancyBootstrapper`](https://tenancyforlaravel.com/docs/v3/tenancy-bootstrappers) , your Redis databases will be automatically separated for your tenants, and as such, any sessions stored in those Redis databases will be scoped correctly. Using a middleware to prevent session forgery --------------------------------------------- Alternatively, you may use the `Stancl\Tenancy\Middleware\ScopeSessions` middleware on your tenant routes to make sure that any attempts to manipulate the session will result in a 403 unauthorized response. This will work with all storage drivers, **but only assuming you use a domain per tenant.** If you use path identification, you **need** to store sessions in the database (if using multi-DB tenancy), or you need to use single-DB tenancy (which is probably more common with path identification). [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/session-scoping.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Single-database tenancy | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Single-database tenancy ======================= Single-database tenancy comes with lower devops complexity, but larger code complexity than multi-database tenancy, since you have to scope things manually, and won't be able to integrate some third-party packages. It is preferable when you have too many shared resources between tenants, and don't want to make too many cross-database queries. To use single-database tenancy, make sure you disable the `DatabaseTenancyBootstrapper` which is responsible for switching database **connections** for tenants. You can still use the other [tenancy bootstrappers](https://tenancyforlaravel.com/docs/v3/tenancy-bootstrappers) to separate tenant caches, filesystems, etc. Also make sure you have disabled the database creation jobs (`CreateDatabase`, `MigrateDatabase`, `SeedDatabase` ...) from listening to the `TenantCreated` event. Concepts ======== In single-database tenancy, there are 4 types of models: * your **Tenant** model * primary models — models that **directly** belongTo tenants * secondary models — models that **indirectly** belongTo tenants * e.g. **Comment** belongsTo **Post** belongsTo **Tenant** * or more complex, **Vote** belongsTo **Comment** belongsTo **Post** belongsTo **Tenant** * global models — models that are **not scoped** to any tenant whatsoever To scope your queries correctly, apply the `Stancl\Tenancy\Database\Concerns\BelongsToTenant` trait **on primary models**. This will ensure that all calls to your parent models are scoped to the current tenant, and that **calls to their child relations are scoped through the parent relationships**. And that's it. Your models are now automatically scoped to the current tenant, and not scoped at all when there's no current tenant (e.g. in a central admin panel). However, there's one edge case to keep in mind. Consider the following set-up: class Post extends Model { use BelongsToTenant; public function comments() { return $this->hasMany(Comment::class); } } class Comment extends Model { public function post() { return $this->belongsTo(Post::class); } } Looks correct, but you might still accidentally access another tenant's comments. If you use this: Comment::all(); then the model has no way of knowing how to scope that query, since it doesn't directly belong to the tenant. Also note that in practice, you really shouldn't be doing this much. You should ideally access secondary models through parent models in every single case. However, sometimes you might have a use case where you **really need to do that** in the tenant context. For that reason, we also provide you with a `BelongsToPrimaryModel` trait, which lets you scope calls like the one above to the current tenant, by loading the parent relationship — which gets automatically scoped to the current tenant — on them. So, to give you an example, you would do this: class Comment extends Model { use BelongsToPrimaryModel; public function getRelationshipToPrimaryModel(): string { return 'post'; } public function post() { return $this->belongsTo(Post::class); } } And this will automatically scope the `Comment::all()` call to the current tenant. Note that the limitation of this is that you **need to be able to define a relationship to a primary model**, so if you need to do this on the "Vote" in _**Vote** belongsTo **Comment** belongsTo **Post** belongsTo **Tenant**,_ you need to define some strange relationship. Laravel supports `HasOneThrough`, but not `BelongsToThrough`, so you'd need to do some hacks around that. For that reason, I recommend avoiding these `Comment::all()`\-type queries altogether. Database considerations ======================= ### Unique indexes If you'd have a unique index such as: $table->unique('slug'); in a standard non-tenant, or multi-database-tenant, application, you need to scope this unique index to the tenant, meaning you'd do **this on primary models:** $table->unique(['tenant_id', 'slug']); and this on **secondary models:** // Imagine we're in a 'comments' table migration $table->unique(['post_id', 'user_id']); ### Validation The `unique` and `exists` validation rules of course aren't scoped to the current tenant, so you need to scope them manually like this: Rule::unique('posts', 'slug')->where('tenant_id', tenant('id')); If that feels like a chore, you may use the `Stancl\Tenancy\Database\Concerns\HasScopedValidationRules` trait on your custom [Tenant](https://tenancyforlaravel.com/docs/v3/tenants) model to add methods for these two rules. You'll be able to use these two methods: // You may retrieve the current tenant using the tenant() helper. // $tenant = tenant(); $rules = [\ 'id' => $tenant->exists('posts'),\ 'slug' => $tenant->unique('posts'),\ ] ### Low-level database queries And the final thing to keep in mind is that `DB` facade calls, or any other types of direct database queries, of course won't be scoped to the current tenant. The package can only provide scoping logic for the abstraction logic that Eloquent is, it can't do anything with low level database queries. Be careful with using them. Making global queries --------------------- To disable the tenant scope, simply add `withoutTenancy()` to your query. Customizing the column name --------------------------- If you'd like to customize the column name to use e.g. `team_id` instead of `tenant_id` — if that makes more sense given your business terminology — you can do that by setting this static property in a service provider or some such class: use Stancl\Tenancy\Database\Concerns\BelongsToTenant; BelongsToTenant::$tenantIdColumn = 'team_id'; Note that this is universal to all your primary models, so if you use `team_id` somewhere, you use it everywhere — you can't use both `team_id` and `tenant_id`. [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/single-database-tenancy.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Tenant identification | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Tenant identification ===================== The package lets you identify tenants using the following methods: * Domain identification (`acme.com`) * Subdomain identification (`acme.yoursaas.com`) * Domain OR subdomain identification (both of the above) * Path identification (`yoursaas.com/acme/dashboard`) * Request data identification (`yoursaas.com/users?tenant=acme` — or using request headers) However, **you're free to write additional tenant resolvers.** All of the identification methods mentioned above come with their own middleware. You can read more about each identification method below. Domain identification --------------------- To use this identification method, make sure your tenant model uses the `HasDomains` trait. Be sure to read the [Domains](https://tenancyforlaravel.com/docs/v3/domains) page of the documentation. The relationship is `Tenant hasMany Domain`. Store the hostnames in the `domain` column of the `domains` table. This identification method comes with the `Stancl\Tenancy\Middleware\InitializeTenancyByDomain` middleware. Subdomain identification ------------------------ This is the exact same as domain identification, except you store the subdomains in the `domain` column of the `domains` table. The benefit of this approach rather than storing the subdomain's full hostname in the `domain` column is that you can use this subdomain on **any** of your central domains. The middleware for this method is `Stancl\Tenancy\Middleware\InitializeTenancyBySubdomain`. Combined domain/subdomain identification ---------------------------------------- If you'd like to use subdomains and domains at the same time, use the `Stancl\Tenancy\Middleware\InitializeTenancyByDomainOrSubdomain` middleware. Records that contain **dots** in the `domain` column will be treated as domains/hostnames (eg. `foo.bar.com`) and records that don't contain any dots will be treated as subdomains (eg. `foo`). Path identification ------------------- Some applications will want to use a single domain, but use paths to identify the tenant. This would be when you want customers to use your branded product rather than giving them a whitelabel product that they can use on their own domains. To do this, use the `Stancl\Tenancy\Middleware\InitializeTenancyByPath` middleware **and make sure your routes are prefixed with `/{tenant}`**. use Stancl\Tenancy\Middleware\InitializeTenancyByPath; Route::group([\ 'prefix' => '/{tenant}',\ 'middleware' => [InitializeTenancyByPath::class],\ ], function () { Route::get('/foo', 'FooController@index'); }); If you'd like to customize the name of the argument (e.g. use `team` instead of `tenant`, look into the `PathTenantResolver` for the public static property). Request data identification --------------------------- You might want to identify tenants based on request data (headers or query parameters). Applications with SPA frontends and API backends may want to use this approach. The middleware for this identification method is `Stancl\Tenancy\Middleware\InitializeTenancyByRequestData`. You may customize what this middleware looks for in the request. By default, it will look for the `X-Tenant` header. If the header is not found, it will look for the `tenant` query parameter. If you'd like to use a different header, change the static property: use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData; InitializeTenancyByRequestData::$header = 'X-Team'; If you'd like to only use the query parameter identification, set the header static property to null: use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData; InitializeTenancyByRequestData::$header = null; If you'd like to disable the query parameter identification and only ever use the header, set the static property for the parameter to null: use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData; InitializeTenancyByRequestData::$queryParameter = null; Manually identifying tenants ---------------------------- See the [manual initialization page](https://tenancyforlaravel.com/docs/v3/manual-initialization) to see how to identify tenants manually. Customizing onFail logic ------------------------ Each identification middleware has a [static `$onFail` property](https://tenancyforlaravel.com/docs/v3/configuration/#static-properties) that can be used to customize the behavior that should happen when a tenant couldn't be identified. \Stancl\Tenancy\Middleware\InitializeTenancyByDomain::$onFail = function ($exception, $request, $next) { return redirect('https://my-central-domain.com/'); }; [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/tenant-identification.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # User impersonation | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) User impersonation ================== This package comes with a feature that lets you impersonate users inside tenant databases. This feature works with **any identification method** and **any stateful auth guard** — even if you use multiple. > Note: If you're currently using a non-stateful auth guard (e.g., Laravel Sanctum's guard), you can still utilize user impersonation by passing a stateful guard to `tenancy()->impersonate()` (e.g. the `'web'` guard). How it works ------------ You generate an impersonation token and store it in the central database, in the `tenant_user_impersonation_tokens` table. Each record in the table holds the following data: * The token value (128 character string) * The tenant's id * The user's id * The name of the auth guard * The URL to redirect to after the impersonation takes place You visit an impersonation route that you create — though little work is needed on your side, your route will mostly just call a method provided by the feature. This route is a **tenant route**, meaning it's on the tenant domain if you use domain identification, or prefixed with the tenant id if you use path identification. This route tries to find a record in that table based on the token, and if it's valid, it authenticates you with the stored user id against the auth guard and redirects you to the stored URL. If the impersonation succeeds, the token is deleted from the database. All tokens expire after 60 seconds by default, and this TTL can be customized — see the section at the very bottom. Enabling the feature -------------------- To enable this feature, go to your `config/tenancy.php` file and make sure the following class is in your `features` part of the config: Stancl\Tenancy\Features\UserImpersonation::class, Next, publish the migration for creating the table with impersonation tokens: php artisan vendor:publish --tag=impersonation-migrations And finally, run the migration: php artisan migrate Usage ----- First, you need to create a tenant route that looks like this: use Stancl\Tenancy\Features\UserImpersonation; // We're in your tenant routes! Route::get('/impersonate/{token}', function ($token) { return UserImpersonation::makeResponse($token); }); // Of course use a controller in a production app and not a Closure route. // Closure routes cannot be cached. Note that the route path or name are completely up to you. The only logic that the package does is generating tokens, verifying tokens, and doing the impersonated user log in. Then, to use impersonation in your app, generate a token like this: // Let's say we want to be redirected to the dashboard // after we're logged in as the impersonated user. $redirectUrl = '/dashboard'; $token = tenancy()->impersonate($tenant, $user->id, $redirectUrl); And redirect the user (or, presumably an "admin") to the route you created. ### Domain identification // Note: This is not part of the package, it's up to you to implement // a concept of "primary domains" if you need them. Or maybe you use // one domain per tenant. The package lets you do anything you want. $domain = $tenant->primary_domain; return redirect("https://$domain/impersonate/{$token->token}"); ### Path identification // Make sure you use the correct prefix for your routes. return redirect("{$tenant->id}/impersonate/{$token->token}"); And that's it. The user will be redirected to your impersonation route, logged in as the impersonated user, and finally redirected to your redirect URL. ### Custom auth guards > Note: The auth guard used by user impersonation has to be stateful (it has to implement the `Illuminate\Contracts\Auth\StatefulGuard` interface). If you're using multiple auth guards, you may want to specify what auth guard the impersonation logic should use. To do this, simply pass the auth guard name as the fourth argument to the `impersonate()` method. So to expand on our example above: tenancy()->impersonate($tenant, $user->id, $redirectUrl, 'jwt'); Customization ------------- You may customize the TTL of impersonation tokens by setting the following static property to the amount of seconds you want to use: Stancl\Tenancy\Features\UserImpersonation::$ttl = 120; // 2 minutes [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/features/user-impersonation.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Manual tenancy mode | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Manual mode =========== > See: [Automatic mode](https://tenancyforlaravel.com/docs/v3/automatic-mode) If you wish to use the package only to keep track of the current tenant and make the application tenant-aware manually — without using the [Tenancy bootstrappers](https://tenancyforlaravel.com/docs/v3/tenancy-bootstrappers) , you can absolutely do that. You may use the `Stancl\Tenancy\Database\Concerns\CentralConnection` and `Stancl\Tenancy\Database\Concerns\TenantConnection` model traits to make models explicitly use the given connections. To create the tenant connection, set up the `CreateTenantConnection` listener: // app/Providers/TenancyServiceProvider.php Events\TenancyInitialized::class => [\ Listeners\CreateTenantConnection::class,\ ], This approach is generally discouraged, because you lose all of the benefits of the [automatic mode](https://tenancyforlaravel.com/docs/v3/automatic-mode) , but **there won't be any issues with the package** if you decide to use the manual mode. You might not be able to integrate other packages as easily, but if for whatever reason it makes more sense for your project to use this approach, feel comfortable to do so. [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/manual-mode.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Synced resources between tenants | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Synced resources between tenants ================================ If you'd like to share certain resources, usually users, between tenant databases, you can use our resource syncing feature. This will let you **sync specific columns between specific tenant databases and the central database.** This is a relatively complex feature, so before implementing it, make sure you really need it. You only need this feature if you're using multi-database tenancy and need to sync specific resources (like users) between different tenants' databases. Database -------- The resource exists in the central database, for example a `users` table. Another table exists in the tenants' databases. It can use the same name as the central database or a different name — up to you. Then there's a pivot table in the central database that maps the resource (`users` in our case) to tenants. The resource isn't synced with all tenant databases — that would be unwanted, e.g. a user typically only exists in select few tenants. Concepts -------- You will need two models for the resource. One for the tenant database and one for the central database. The tenant model must implement the `Syncable` interface and the central model must implement the `SyncMaster` interface. `SyncMaster` is an extension of `Syncable`, it requires one extra method — the relationship to tenants, to know which tenants also have this resource. Both models must use the `ResourceSyncing` trait. This trait makes sure that a `SyncedResourceSaved` event is fired whenever the model is saved. The `UpdateSyncedResource` listener will update the resource in the central database and in all tenant databases where the resource exists. The listener is registered in your `TenancyServiceProvider`. It is important to note that when a model is updated or created as a result of being synchronised, that model is called with `withoutEvents` and as such if you rely on the saving or creating events you will need to implement this in some other way. An important requirement of the `Syncable` interface is the `getSyncedAttributeNames()` method. You don't want to sync all columns (or more specifically, attributes, since we're talking about Eloquent models — **accessors & mutators are supported**). In the `users` example, you'd likely only want to sync attributes like the name, email and password, while keeping tenant-specific (or workspace-specific/team-specific, whatever makes sense in your project's terminology) attributes independent. The resource needs to have the same global ID in the central database and in tenant databases. How it works ------------ Let's write an example implementation: use Stancl\Tenancy\Database\Models\Tenant as BaseTenant; use Stancl\Tenancy\Database\Models\TenantPivot; class Tenant extends BaseTenant implements TenantWithDatabase { use HasDatabase, HasDomains; public function users() { return $this->belongsToMany(CentralUser::class, 'tenant_users', 'tenant_id', 'global_user_id', 'id', 'global_id') ->using(TenantPivot::class); } } class CentralUser extends Model implements SyncMaster { // Note that we force the central connection on this model use ResourceSyncing, CentralConnection; protected $guarded = []; public $timestamps = false; public $table = 'users'; public function tenants(): BelongsToMany { return $this->belongsToMany(Tenant::class, 'tenant_users', 'global_user_id', 'tenant_id', 'global_id') ->using(TenantPivot::class); } public function getTenantModelName(): string { return User::class; } public function getGlobalIdentifierKey() { return $this->getAttribute($this->getGlobalIdentifierKeyName()); } public function getGlobalIdentifierKeyName(): string { return 'global_id'; } public function getCentralModelName(): string { return static::class; } public function getSyncedAttributeNames(): array { return [\ 'name',\ 'password',\ 'email',\ ]; } } class User extends Model implements Syncable { use ResourceSyncing; protected $guarded = []; public $timestamps = false; public function getGlobalIdentifierKey() { return $this->getAttribute($this->getGlobalIdentifierKeyName()); } public function getGlobalIdentifierKeyName(): string { return 'global_id'; } public function getCentralModelName(): string { return CentralUser::class; } public function getSyncedAttributeNames(): array { return [\ 'name',\ 'password',\ 'email',\ ]; } } // Pivot table migration in the central database use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateTenantUsersTable extends Migration { public function up() { Schema::create('tenant_users', function (Blueprint $table) { $table->increments('id'); $table->string('tenant_id'); $table->string('global_user_id'); $table->unique(['tenant_id', 'global_user_id']); $table->foreign('tenant_id')->references('id')->on('tenants')->onUpdate('cascade')->onDelete('cascade'); $table->foreign('global_user_id')->references('global_id')->on('users')->onUpdate('cascade')->onDelete('cascade'); }); } public function down() { Schema::dropIfExists('tenant_users'); } } Here's how it will work: * You create a user in the central database. It only exists in the central DB. $user = CentralUser::create([\ 'global_id' => 'acme',\ 'name' => 'John Doe',\ 'email' => 'john@localhost',\ 'password' => 'secret',\ 'role' => 'superadmin', // unsynced\ ]); * Now you create a user **with the same global id** in a tenant's database: tenancy()->initialize($tenant); // Create the same user in tenant DB $user = User::create([\ 'global_id' => 'acme',\ 'name' => 'John Doe',\ 'email' => 'john@localhost',\ 'password' => 'secret',\ 'role' => 'commenter', // unsynced\ ]); * You update some attribute on the tenant: $user->update([\ 'name' => 'John Foo', // synced\ 'email' => 'john@foreignhost', // synced\ 'role' => 'admin', // unsynced\ ]); * The central user's `name` and `email` have changed. If you create more tenants and create the user in those tenants' databases, the changes will be synced between all these tenants' databases and the central database. Creating the user inside a tenant's database will copy the resource 1:1 to the central database, including the unsynced columns (here they act as default values). Attaching resources to tenants ------------------------------ You can see that in the example above we're using the `TenantPivot` model for the BelongsToMany relationship. This lets us cascade synced resources from the central database to tenants: $user = CentralUser::create(...); $user->tenants()->attach($tenant); Attaching a tenant to a user will copy even the unsynced columns (they act as default values), similarly to how creating the user inside the tenant's database will copy the tenant to the central database 1:1. If you'd like to use a custom pivot model, look into the source code of `TenantPivot` to see what to copy (or extend it) if you want to preserve this behavior. Also note that if you create a user in the tenant's database, the global id will be created using the ID generator. If you disable the ID generator for [incrementing tenant ids](https://tenancyforlaravel.com/docs/v3/tenants#incrementing-ids) , you'll need to make some changes. Queueing -------- In production, you almost certainly want to queue the listener that copies the changes to other databases. To do this, change the listener's static property: \Stancl\Tenancy\Listeners\UpdateSyncedResource::$shouldQueue = true; [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/synced-resources-between-tenants.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Integration with other packages | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Integrating with other packages =============================== If you're using the [automatic mode](https://tenancyforlaravel.com/docs/v3/automatic-mode) & [multi-database tenancy](https://tenancyforlaravel.com/docs/v3/multi-database-tenancy) , you'll be able to integrate with other packages easily. * [Integration with Spatie packages](https://tenancyforlaravel.com/docs/v3/integrations/spatie) * [Laravel Horizon](https://tenancyforlaravel.com/docs/v3/integrations/horizon) * [Laravel Passport](https://tenancyforlaravel.com/docs/v3/integrations/passport) * [Laravel Nova](https://tenancyforlaravel.com/docs/v3/integrations/nova) * [Laravel Telescope](https://tenancyforlaravel.com/docs/v3/integrations/telescope) * [Livewire](https://tenancyforlaravel.com/docs/v3/integrations/livewire) * [Laravel Sanctum](https://tenancyforlaravel.com/docs/v3/integrations/sanctum) * [Laravel Sail](https://tenancyforlaravel.com/docs/v3/integrations/sail) * [Vite](https://tenancyforlaravel.com/docs/v3/features/vite-bundler) [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/integrating.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Manual tenancy initialization | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Manual initialization ===================== Sometimes you may want to initialize tenancy manually — that is, not using web middleware, command traits, queue tenancy etc. A common use case for this is if you need to use `artisan tinker` for a specific tenant. For that, use the `initialize()` method on `Stancl\Tenancy\Tenancy`. You can resolve the `Tenancy` instance out of the container using the `tenancy()` helper. $tenant = Tenant::find('some-id'); tenancy()->initialize($tenant); [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/manual-initialization.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Queues | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Queues ====== If you're using the `QueueTenancyBootstrapper`, queued jobs dispatched from the tenant context will be automatically tenant-aware. The jobs will be stored centrally — if you're using the database queue driver, they will be stored in the `jobs` table in the central database. And tenancy will be initialized for the current tenant prior to the job being processed. **However**, note that if you're using the `DatabaseTenancyBootstrapper` and the `database` queue driver, or the `RedisTenancyBootstrapper` and the `redis` queue driver, you will need to make sure the jobs don't get dispatched into the tenant context for these drivers. ### Database queue driver To force the database queue driver to use the central connection, open your `queue.connections.database` config and add the following line: 'connection' => 'central', (Replace `central` with the name of your central database connection.) ### Redis queue driver Make sure the connection used by the queue is not in `tenancy.redis.prefixed_connections`. Central queues -------------- Jobs dispatched from the central context will remain central. However, it's recommended not to mix queue **connections** for central & tenant jobs due to potential leftover global state, e.g. central jobs thinking they're in the previous tenant's context. To dispatch a job such that it will run centrally under all circumstances, create a new queue connection and set the `central` key to `true`. For example: // queue.connections 'central' => [\ 'driver' => 'database',\ 'table' => 'jobs',\ 'queue' => 'default',\ 'retry_after' => 90,\ 'central' => true, // <---\ ], And use this connection like this: dispatch(new SomeJob(...))->onConnection('central'); [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/queues.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Testing | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Testing ======= > If you're a sponsor, you can get an opinionated, but automatic testing setup on the site with exclusive content for sponsors: https://sponsors.tenancyforlaravel.com/frictionless-testing-setup TODO: Review Events ------ Keep in mind that the package makes heavy use of events, so if you use `Event::fake()` anywhere in your tests, tenancy initialization and related processes might break. For that reason, try to be selective about faking tests. Use for example: Event::fake([MyEvent::class]); rather than Event::fake(); Central app ----------- To test your central app, just write normal Laravel tests. Tenant app ---------- Note: If you're using multi-database tenancy & the automatic mode, it's not possible to use `:memory:` SQLite databases or the `RefreshDatabase` trait due to the switching of default database. To test the tenant part of the application, create a tenant in the `setUp()` method and initialize tenancy. You may also want to do something like this: class TestCase // extends ... { protected $tenancy = false; public function setUp(): void { parent::setUp(); if ($this->tenancy) { $this->initializeTenancy(); } } public function initializeTenancy() { $tenant = Tenant::create(); tenancy()->initialize($tenant); } // ... } And in your individual test cases: class FooTest extends TestCase { protected $tenancy = true; /** @test */ public function some_test() { $this->assertTrue(...); } } Or you may want to create a separate TestCase class for tenant tests, for better organization. [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/testing.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Laravel Horizon integration | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Laravel Horizon =============== > Note: **Horizon is only accessible on the central domain**. You can separate the jobs by [tagging them with tenant IDs](#tags) > . Make sure your [queues](https://tenancyforlaravel.com/docs/v3/queues) are configured correctly before using this. Tags ---- You may add the current tenant's ID to your job tags by defining a `tags` method on the class: /** * Get the tags that should be assigned to the job. * * @return array */ public function tags() { return [\ 'tenant:' . tenant('id'),\ ]; } [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/integrations/horizon.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Integration with Spatie packages | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Integration with Spatie packages ================================ **laravel-activitylog** ----------------------- > Note: The package requires logged models to have integer IDs. We recommend extra security measures when using integer IDs for tenants. Because the IDs become enumerable, they get vulnerable to enumeration attacks (which UUIDs are safe against). For example, to use the LogsActivity trait on the Tenant model, modify the model to have an integer ID. ### For the tenant app: * Set the `database_connection` key in `config/activitylog.php` to `null`. This makes activitylog use the default connection. * Publish the migrations and move them to `database/migrations/tenant`. (And, of course, don't forget to run `artisan tenants:migrate`.) ### For the central app: * Set the `database_connection` key in `config/activitylog.php` to the name of your central database connection. **laravel-permission** ---------------------- Install the package like usual, but publish the migrations and move them to `migrations/tenant`: php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="migrations" mv database/migrations/*_create_permission_tables.php database/migrations/tenant Next, add the following listeners to the `TenancyBootstrapped` and `TenancyEnded` events to `events()` in your `TenancyServiceProvider`: Events\TenancyBootstrapped::class => [\ function (Events\TenancyBootstrapped $event) {\ $permissionRegistrar = app(\Spatie\Permission\PermissionRegistrar::class);\ $permissionRegistrar->cacheKey = 'spatie.permission.cache.tenant.' . $event->tenancy->tenant->getTenantKey();\ }\ ], Events\TenancyEnded::class => [\ function (Events\TenancyEnded $event) {\ $permissionRegistrar = app(\Spatie\Permission\PermissionRegistrar::class);\ $permissionRegistrar->cacheKey = 'spatie.permission.cache';\ }\ ], Alternatively, create a bootstrapper: class SpatiePermissionsBootstrapper implements TenancyBootstrapper { public function __construct( protected PermissionRegistrar $registrar, ) {} public function bootstrap(Tenant $tenant): void { $this->registrar->cacheKey = 'spatie.permission.cache.tenant.' . $tenant->getTenantKey(); } public function revert(): void { $this->registrar->cacheKey = 'spatie.permission.cache'; } } The reason for this is that spatie/laravel-permission caches permissions & roles to save DB queries, which means that we need to separate the permission cache by tenant. We also need to reset the cache key when tenancy ends so that the tenant's cache key isn't used in the central app. **laravel-medialibrary** ------------------------ To generate the correct media URLs for tenants, create a custom URL generator class extending `Spatie\MediaLibrary\Support\UrlGenerator\DefaultUrlGenerator` and override the `getUrl()` method: use Spatie\MediaLibrary\Support\UrlGenerator\DefaultUrlGenerator; class TenantAwareUrlGenerator extends DefaultUrlGenerator { public function getUrl(): string { $url = asset($this->getPathRelativeToRoot()); $url = $this->versionUrl($url); return $url; } } Then, change the `url_generator` in the media-library config to the custom one (make sure to import it): 'url_generator' => TenantAwareUrlGenerator::class, **laravel-login-link** ---------------------- After installing the package, publish the config file: php artisan vendor:publish --tag="login-link-config" After that, add your [tenant identification](https://tenancyforlaravel.com/docs/v3/tenant-identification) middlewares to the config file's `middleware` key: 'middleware' => [\ 'web',\ // Add whatever tenant identification middlewares you use in your tenant routes\ InitializeTenancyByDomain::class,\ PreventAccessFromCentralDomains::class,\ ], [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/integrations/spatie.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Laravel Sail integration | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Laravel Sail ============ > Note: This guide covers Sail integration using MySQL. The steps described in the guide will need adjustments if you're using a database other than MySQL. > > The default Sail user's name is determined by the `DB_USERNAME` variable in your `.env`. For this guide, we'll be using the username `sail`. The default Sail user can only perform the create, read, update and delete operations in the central database. To allow the user to perform these operations in the tenant databases too, log in to Sail's MySQL shell as the root user (`docker compose exec mysql bash`, then `mysql -u root -p` – by default, the password is determined by the `DB_PASSWORD` variable in your `.env`) and grant the Sail user access to all databases by running the following statements: GRANT ALL PRIVILEGES ON *.* TO 'sail'@'%' WITH GRANT OPTION; FLUSH PRIVILEGES; You have to grant the privileges every time you re-run a container. To automate granting the privileges, create an SQL file with the previously mentioned SQL statements to grant all privileges to the Sail user. Then, add the path to the SQL file to `docker-compose.yml`'s MySQL volumes: mysql: # ... volumes: # ... - 'PATH_TO_THE_SQL_FILE:/docker-entrypoint-initdb.d/SQL_FILE_NAME.sql' [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/integrations/sail.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Laravel Nova integration | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Laravel Nova ============ In the central app ------------------ If you wish to use Laravel Nova in the central application (to manage tenants), you need to make a small change to the Nova migrations, they expect your model primary keys to always be unsigned big integers, but your tenants might be using `string` ids. You can find the full Nova setup for managing tenants in the [SaaS boilerplate](/saas-boilerplate) . It also implements Nova for **both the central and tenant parts of the app, with separate resources**. In the tenant app ----------------- To use Nova inside of the tenant part of your application, do the following: * Publish the Nova migrations and move them to the `database/migrations/tenant` directory. php artisan vendor:publish --tag=nova-migrations * Prevent Nova from adding its migrations to your central migrations by adding `Nova::ignoreMigrations()` to `NovaServiceProvider::boot()` (Don't do this if you want to use Nova **[both in the central & tenant parts](https://tenancyforlaravel.com/docs/v3/features/universal-routes) ** of the app.) * Add the tenancy middleware to your `nova.middleware` config. Example: 'middleware' => [\ // You can make this simpler by creating a tenancy route group\ InitializeTenancyByDomain::class,\ PreventAccessFromCentralDomains::class,\ 'web',\ Authenticate::class,\ DispatchServingNovaEvent::class,\ BootTools::class,\ Authorize::class,\ ], * In your `NovaServiceProvider`'s `routes()` method, replace the following lines: ->withAuthenticationRoutes() ->withPasswordResetRoutes() with these lines: ->withAuthenticationRoutes([\ // You can make this simpler by creating a tenancy route group\ InitializeTenancyByDomain::class,\ PreventAccessFromCentralDomains::class,\ 'nova',\ ]) ->withPasswordResetRoutes([\ // You can make this simpler by creating a tenancy route group\ InitializeTenancyByDomain::class,\ PreventAccessFromCentralDomains::class,\ 'nova',\ ]) * Set the `domain` in Nova config to `null` Tenant file thumbnails and previews ----------------------------------- To make the file field previews show correctly, you have to call `thumbnail(fn ($value, $disk) => tenant_asset($value))` and `preview(fn ($value, $disk) => tenant_asset($value))` on the field. For example: Avatar::make('Avatar', 'photo') ->disk('public') ->thumbnail(fn ($value, $disk) => tenant_asset($value)), ->preview(fn ($value, $disk) => tenant_asset($value)), Important: make sure to update `TenantAssetsController::$tenancyMiddleware` to the identification middleware of your choice. E.g. if you're using InitializeByDomainOrSubdomain in your app, set `TenantAssetsController::$tenancyMiddleware` to InitializeByDomainOrSubdomain in TenancyServiceProvider's `boot()` method: // In App\Providers\TenancyServiceProvider public function boot(): void { // ... TenantAssetsController::$tenancyMiddleware = InitializeTenancyByDomainOrSubdomain::class; } [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/integrations/nova.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Laravel Passport integration | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Laravel Passport ================ > **Tip:** If you just want to write an SPA application but don't need an API for some other use (e.g., a mobile app), you can avoid a lot of the complexity of writing SPAs by using [Inertia.js](https://inertiajs.com/) > . > > **Another tip:** Using Passport only in the central application doesn't require any additional configuration. You can just install it following [the official Laravel Passport documentation](https://laravel.com/docs/9.x/passport) > . **Using Passport in the tenant application only** ------------------------------------------------- > **Note:** Don't use the `passport:install` command. The command creates the encryption keys & two clients in the central application. Instead of that, we'll generate the keys and create the clients manually later. To use Passport inside the tenant part of your application, you may do the following. 1. Publish the Passport migrations by running `php artisan vendor:publish --tag=passport-migrations` and move them to your tenant migration directory (`database/migrations/tenant/`). 2. Publish the Passport config by running `php artisan vendor:publish --tag=passport-config`. If you're using Passport 10.x, make Passport use the default database connection by setting the storage database connection to `null`. The `passport:keys` command puts the keys in the `storage/` directory by default – you can change that by setting the key path in the config. return [\ 'storage' => [ // Needed only when using Passport 10.x\ 'database' => [\ 'connection' => null,\ ],\ ],\ 'key_path' => env('OAUTH_KEY_PATH', 'storage') // This is optional\ ]; 3. Prevent Passport migrations from running in the central application by adding `Passport::ignoreMigrations()` to the `register()` method in your `AuthServiceProvider`. 4. If you're using Passport 10.x, register the Passport routes in your `AuthServiceProvider` by adding the following code to the provider's `boot()` method: Passport::routes(null, ['middleware' => [\ InitializeTenancyByDomain::class, // Or other identification middleware of your choice\ PreventAccessFromCentralDomains::class,\ ]]); 5. If you're using Passport 11.x, disable the automatic Passport route registering in your `AuthServiceProvider` by adding `Passport::ignoreRoutes();` to the `register()` method. Then, register the Passport routes manually by adding the following code to the `boot()` method: Route::group([\ 'as' => 'passport.',\ 'middleware' => [\ InitializeTenancyByDomain::class, // Use tenancy initialization middleware of your choice\ PreventAccessFromCentralDomains::class,\ ],\ 'prefix' => config('passport.path', 'oauth'),\ 'namespace' => 'Laravel\Passport\Http\Controllers',\ ], function () { $this->loadRoutesFrom(__DIR__ . "/../../vendor/laravel/passport/src/../routes/web.php"); }); 6. Apply Passport migrations by running `php artisan tenants:migrate`. 7. Set up [the encryption keys](#passport-encryption-keys) . **Using Passport in both the tenant and the central application** ----------------------------------------------------------------- To use Passport in both the tenant and the central application: 1. Follow [the steps for using Passport in the tenant appliction](#using-passport-in-the-tenant-application-only) . 2. Copy the Passport migrations to the central application, so that the Passport migrations are in both the central and the tenant application. 3. Remove `Passport::ignoreMigrations()` from the `register()` method in your `AuthServiceProvider` (if it is there). 4. In your `AuthServiceProvider`'s `boot()` method (where you registered the Passport routes), add the `'universal'` middleware to the Passport routes, and remove the `PreventAccessFromCentralDomains::class` middleware. The related code in your `boot()` method should look like this: // Passport 10.x Passport::routes(null, ['middleware' => [\ 'universal',\ InitializeTenancyByDomain::class\ ]]); // Passport 11.x Route::group([\ 'as' => 'passport.',\ 'middleware' => [\ 'universal',\ InitializeTenancyByDomain::class\ ],\ 'prefix' => config('passport.path', 'oauth'),\ 'namespace' => 'Laravel\Passport\Http\Controllers',\ ], function () { $this->loadRoutesFrom(__DIR__ . "/../../vendor/laravel/passport/src/../routes/web.php"); }); 5. Enable [universal routes](https://tenancyforlaravel.com/docs/v3/features/universal-routes) to make Passport routes accessible to both apps. **Passport encryption keys** ---------------------------- ### **Shared keys** To generate a single Passport key pair for the whole app, create Passport clients for your tenants by adding the following code to your [tenant database seeder](https://tenancyforlaravel.com/docs/v3/configuration/#seeder-parameters) . public function run() { $client = new ClientRepository(); $client->createPasswordGrantClient(null, 'Default password grant client', 'http://your.redirect.path'); $client->createPersonalAccessClient(null, 'Default personal access client', 'http://your.redirect.path'); } _You can set your tenant database seeder class in `config/tenancy.php` file at `seeder_parameters` key._ Then, seed the database and generate the key pair by running `php artisan passport:keys`. ### **Tenant-specific keys** > **Note:** The security benefit of doing this is negligible since you're likely already using the same `APP_KEY` for all tenants. This is a relatively complex approach, so before implementing it, make sure you really want it. **Using shared keys instead is strongly recommended.** > > **Warning:** The usage of tenant specific keys has not been fully tested. [Feel free to contribute to this section.](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/integrations/passport.blade.md) If you want to use a unique Passport key pair for each tenant, there are multiple ways to store and load tenant Passport keys. The most straightforward way is to store them in the `Tenant model` and load them into the Passport configuration using the [Tenant Config](https://tenancyforlaravel.com/docs/v3/features/tenant-config) feature. Then, you can access the keys like `$tenant->passport_public_key`. To achieve that, enable the [Tenant Config](https://tenancyforlaravel.com/docs/v3/features/tenant-config) feature, and configure the storage-to-config mapping in the `boot` method of your `TenancyServiceProvider` this way: \Stancl\Tenancy\Features\TenantConfig::$storageToConfigMap = [\ 'passport_public_key' => 'passport.public_key',\ 'passport_private_key' => 'passport.private_key',\ ]; [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/integrations/passport.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Laravel Orchid integration | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Laravel Orchid ============== First, set up tenancy following the [quickstart guide](https://tenancyforlaravel.com/docs/v3/quickstart) , and install [Laravel Orchid](https://orchid.software/en/docs/installation/) . To use Orchid both in the central and the tenant app: – Copy the user and Orchid migrations to `migrations\tenant` * Enable [universal routes](https://tenancyforlaravel.com/docs/v3/features/universal-routes) * Add your tenant identification middleware to `config\platform.php` (feel free to use a different identification middleware): 'middleware' => [\ 'public' => ['web', 'universal', InitializeTenancyByDomain::class], // Don't forget to import the middleware\ 'private' => ['web', 'platform', 'universal', InitializeTenancyByDomain::class],\ ], * Add a route to `routes\platform.php`: Route::screen('/', PlatformScreen::class) ->name('platform.index') ->breadcrumbs(function (Trail $trail) { return $trail->push(__('Home'), route('platform.index')); }); * Add 'platform' middleware to your tenant routes (`routes\tenant.php`): Route::middleware([\ 'web',\ 'platform',\ InitializeTenancyByDomain::class,\ PreventAccessFromCentralDomains::class,\ ]); * If listing users in the admin panel throws an exception, change line 55 in the UserListLayout class to `return $user->updated_at?->toDateTimeString()` (add null-safe operator) [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/integrations/orchid.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Laravel Telescope integration | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Laravel Telescope ================= Enable the _Telescope tags_ feature in the `tenancy.features` config to have all Telescope requests tagged with the current tenant's id. Note that Telescope (& its migrations) will be part of the central app. [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/integrations/telescope.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Livewire integration | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Livewire ======== Open the `config/livewire.php` file and change this: 'middleware_group' => ['web'], to this: 'middleware_group' => [\ 'web',\ 'universal',\ InitializeTenancyByDomain::class, // or whatever tenancy middleware you use\ ], In Livewire 3, the configuration key `middleware_group` has been removed, so instead add the following in `TenancyServiceProvider` (or any other provider): public function boot(): void { // ... Livewire::setUpdateRoute(function ($handle) { return Route::post('/livewire/update', $handle) ->middleware( 'web', 'universal', InitializeTenancyByDomain::class, // or whatever tenancy middleware you use ); }); } To make file uploads work on Livewire 3, set the following in any service provider: // specify the right identification middleware FilePreviewController::$middleware = ['web', 'universal', InitializeTenancyByDomain::class]; And change `livewire.temporary_file_upload.middleware` to include the tenancy middleware as well: // config/livewire.php 'livewire.temporary_file_upload.middleware' => ['throttle:60,1', 'universal', InitializeTenancyByDomain::class], Now you can use Livewire both in the central app and the tenant app. Also make sure to enable [universal routes](https://tenancyforlaravel.com/docs/v3/features/universal-routes) . And if you're using file uploads, read the [Real-time facades](https://tenancyforlaravel.com/docs/v3/realtime-facades) page of the documentation. Livewire uses real-time facades in the uploading logic. [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/integrations/livewire.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Tenant attribute encryption | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Tenant attribute encryption =========================== To encrypt attributes on the Tenant model, store them in [custom columns](https://tenancyforlaravel.com/docs/v3/tenants/#custom-columns) and cast the attributes to `'encrypted'`, or your custom encryption cast. For example, we'll encrypt the tenant's database credentials – `tenancy_db_username` and `tenancy_db_password`. We need to create custom columns for these attributes, because by default, they are stored in the virtual `data` column. * Add custom columns to the tenants table (we recommend making the string size at least 512 characters, so the string is capable of containing the encrypted data, they also need to be `nullable` since they are filled after creation): string('id')->primary(); // Your custom columns $table->string('tenancy_db_username', 512)->nullable(); $table->string('tenancy_db_password', 512)->nullable(); $table->timestamps(); $table->json('data')->nullable(); }); } } * Define the custom columns on the Tenant model: public static function getCustomColumns(): array { return [\ 'id',\ 'tenancy_db_username',\ 'tenancy_db_password',\ ]; } * Then define casts for the attributes on the model (using [Laravel's encrypted casts](https://laravel.com/docs/9.x/eloquent-mutators#encrypted-casting) , or your custom casts): protected $casts = [\ 'tenancy_db_username' => 'encrypted',\ 'tenancy_db_password' => 'encrypted',\ ]; [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/tenant-attribute-encryption.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Console commands | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Console commands ================ The package comes with some useful artisan commands. Tenant-aware commands run for all tenants by default. The commands also have the `--tenants` option which lets you specify IDs of the tenants for which the command will run. > Note: To include multiple tenants using CLI, you can use multiple `--tenants=<...>` options. If you're calling the command using `Artisan::call()`, `--tenants` has to be an array. **Migrate** (tenant-aware) -------------------------- The `tenants:migrate` command migrates databases of your tenants. php artisan tenants:migrate --tenants=8075a580-1cb8-11e9-8822-49c5d8f8ff23 > Note: By default, the migrations should be in database/migrations/tenant. If you wish to use a different path, you may use the `--path` option. You can also specify the default parameters for the command [in the tenancy config](https://tenancyforlaravel.com/docs/v3/configuration#migration-parameters) **Rollback & seed** (tenant-aware) ---------------------------------- * Rollback: `tenants:rollback` * Seed: `tenants:seed` > Note: You can configure the default parameters for `tenants:seed` (e.g. use a custom tenant seeder) in [the tenancy config](https://tenancyforlaravel.com/docs/v3/configuration#seeder-parameters) **Migrate fresh** (tenant-aware) -------------------------------- This package also offers a simplified, tenant-aware version of `migrate:fresh`. It runs `db:wipe` and `tenants:migrate` on the tenant's database. You may use it like this: php artisan tenants:migrate-fresh --tenants=8075a580-1cb8-11e9-8822-49c5d8f8ff23 **Run** (tenant-aware) ---------------------- You can use the `tenants:run` command to run your own commands for tenants. If your command's signature were `email:send {--queue} {--subject=} {body}`, you would run this command like this: php artisan tenants:run email:send --tenants=8075a580-1cb8-11e9-8822-49c5d8f8ff23 --option="queue=1" --option="subject=New Feature" --argument="body=We have launched a new feature. ..." or using `Artisan::call()`: Artisan::call('tenants:run', [\ 'commandname' => 'email:send', // String\ '--tenants' => ['8075a580-1cb8-11e9-8822-49c5d8f8ff23'] // Array\ '--option' => ['queue=1', 'subject=New Feature'] // Array\ '--argument' => ['body=We have launched a new feature.'] // Array\ ]) **List** -------- The `tenants:list` command lists all existing tenants. php artisan tenants:list Listing all tenants. [Tenant] id: dbe0b330-1a6e-11e9-b4c3-354da4b4f339 @ localhost [Tenant] id: 49670df0-1a87-11e9-b7ba-cf5353777957 @ dev.localhost **Selectively clearing tenant cache** ------------------------------------- You can delete specific tenants' cache by using the `--tags` option on `cache:clear`: php artisan cache:clear --tags=tenantdbe0b330-1a6e-11e9-b4c3-354da4b4f339 The tag is derived from `config('tenancy.cache.tag_base') . $id`. [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/console-commands.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Cached tenant lookup | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Cached lookup ============= If you're using multiple databases, you may want to avoid making a query to the central database on **each tenant request** — for tenant identification. Even though the queries are very simple, the app has to establish a connection with the central database which is expensive. To avoid this, you may enable caching on the tenant resolvers (all in the `Stancl\Tenancy\Resolvers` namespace): * `DomainTenantResolver` (also used for subdomain identification) * `PathTenantResolver` * `RequestDataTenantResolver` On each of these classes, you may set the following static properties: // TenancyServiceProvider::boot() use Stancl\Tenancy\Resolvers; // enable cache DomainTenantResolver::$shouldCache = true; // seconds, 3600 is the default value DomainTenantResolver::$cacheTTL = 3600; // specify some cache store // null resolves to the default cache store DomainTenantResolver::$cacheStore = 'redis'; Cache invalidation ------------------ Updating and saving a Tenant model's attributes will cause the cached entry for this model to be invalidated when `DomainTenantResolver::$shouldCache` is set to `true`. You may invalidate the cache by calling : app(\Stancl\Tenancy\Resolvers\DomainTenantResolver::class)->invalidateCache($tenant); > Note: When using the domain identification, the key of the cache contains the name of the domain. Make sure to invalidate the cache before making any changes if you intend to update the domain name. [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/cached-lookup.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Laravel Sanctum integration | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Laravel Sanctum =============== > Note: The `sanctum` auth guard can't be used with [user impersonation](https://tenancyforlaravel.com/docs/v3/features/user-impersonation) > because user impersonation supports stateful guards only. Laravel Sanctum works with Tenancy out of the box, with the exception of the `sanctum.csrf-cookie` route. You can make some small changes to make the route work. ### Making the csrf-cookie route work in the tenant app To make the `sanctum.csrf-cookie` route work in the tenant app, do the following: 1. Add `'routes' => false` to the `sanctum.php` config 2. Publish the Sanctum migrations and move them to `migrations/tenant` 3. Make Sanctum not use its migrations in the central app by adding `Sanctum::ignoreMigrations()` to the `register()` method in your `AuthServiceProvider` 4. Add the following code to `routes/tenant.php` to override the original `sanctum.csrf-cookie` route: Route::group(['prefix' => config('sanctum.prefix', 'sanctum')], static function () { Route::get('/csrf-cookie', [CsrfCookieController::class, 'show']) ->middleware([\ 'web',\ InitializeTenancyByDomain::class // Use tenancy initialization middleware of your choice\ ])->name('sanctum.csrf-cookie'); }); ### Making the csrf-cookie route work both in the central and the tenant app To use the `sanctum.csrf-cookie` route in both the central and the tenant apps: 1. Follow the steps in the previous section ("Sanctum's csrf-cookie route in the tenant app") 2. Set up [universal routes](https://tenancyforlaravel.com/docs/v3/features/universal-routes) 3. Remove `Sanctum::ignoreMigrations()` from your `AuthServiceProvider`'s `register()` method 4. Remove `'routes' => false` from the `sanctum.php` config 5. Add the `'universal'` middleware to the `sanctum.csrf-cookie` route in your `routes/tenant.php` [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/integrations/sanctum.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Tenant maintenance mode | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Tenant maintenance mode ======================= You may put specific tenants into maintenance mode using the `MaintenanceMode` trait. Apply it on your [Tenant model](https://tenancyforlaravel.com/docs/v3/tenants) : use Stancl\Tenancy\Database\Concerns\MaintenanceMode; class Tenant extends BaseTenant { use MaintenanceMode; } This will let you use the following method on each tenant object: $tenant->putDownForMaintenance(); To remove specific tenant from maintenance mode: $tenant->update(['maintenance_mode' => null]); Middleware ---------- You will also need to use the `Stancl\Tenancy\Middleware\CheckTenantForMaintenanceMode` middleware on your tenant routes. [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/tenant-maintenance-mode.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Real-time facades | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Real-time facades ================= When using `storage_path()` suffixing (for local filesystem tenancy), each tenant gets a separate subdirectory in `storage/`. This means that storage paths look like this: storage/tenant123/app/foo.png **not** like this: storage/app/tenant123/foo.png This means that the other directories in `storage/` are also tenant-scoped. Importantly, the `framework` directory. The issue with real-time facades -------------------------------- When real-time facades are used, Laravel creates a PHP file with facade-like code, stores it in `storage_path/framework/cache` and autoloads it. Creating framework directories for tenants ------------------------------------------ To solve this, you need to create these directories for tenants. But note that you only need this if: 1. you're using `storage_path()` suffixing (enabled in `tenancy` config) 2. **and** you're using real-time facades You can create these directories by using the [event system](https://tenancyforlaravel.com/docs/v3/event-system) . Use a job pipeline, because you need to initialize tenancy to run this code & you need tenancy initialization to even be possible (you can't initialize tenancy before the tenant's database is created, for example). Add a job like this to your `TenantCreated` job pipeline: tenant = $tenant; } public function handle() { $this->tenant->run(function ($tenant) { $storage_path = storage_path(); mkdir("$storage_path/framework/cache", 0777, true); }); } } [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/realtime-facades.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) --- # Tenant-aware commands | Tenancy for Laravel [![](/assets/img/tenancyforlaravel.svg)](https://tenancyforlaravel.com) Version 3.x [Version 1.x](https://tenancyforlaravel.com/docs/v1) [Version 2.x](https://tenancyforlaravel.com/docs/v2) [Version 3.x](https://tenancyforlaravel.com/docs/v3) [GitHub](https://github.com/stancl/tenancy) Tenant-aware commands ===================== Even though [`tenants:run`](https://tenancyforlaravel.com/docs/v3/console-commands#run) lets you run arbitrary artisan commands for tenants, you may want to have strictly tenant commands. To make a command tenant-aware, utilize the `TenantAwareCommand` trait: class MyCommand extends Command { use TenantAwareCommand; } However, this trait requires you to implement a `getTenants()` method that returns an array of `Tenant` instances. If you don't want to implement the options/arguments yourself, you may use one of these two traits: - `HasATenantsOption` - accepts multiple tenant IDs, optional -- by default the command is executed for all tenants - `HasATenantArgument` - accepts a single tenant ID, required argument These traits implement the `getTenants()` method needed by `TenantAwareCommand`. > Note: If you're using a custom constructor for your command, you need to add `$this->specifyParameters()` at the end for the option/argument traits to take effect. So if you use these traits in combination with `TenantAwareCommand`, you won't have to change a thing in your command: class FooCommand extends Command { use TenantAwareCommand, HasATenantsOption; public function handle() { // } } class BarCommand extends Command { use TenantAwareCommand, HasATenantArgument; public function handle() { // } } ### Custom implementation If you want more control, you may implement this functionality yourself by simply accepting a `tenant_id` argument and then inside `handle()` doing something like this: tenancy()->find($this->argument('tenant_id'))->run(function () { // Your actual command code }); [Edit on GitHub](https://github.com/stancl/tenancy-docs/edit/master/source/docs/v3/tenant-aware-commands.blade.md) #### Documentation * [Tenants](/docs/v3/tenants) * [Event system](/docs/v3/event-system) * [Configuration](/docs/v3/configuration) #### Documentation * [Compared to other packages](/docs/v3/package-comparison) * [Integrations](/docs/v3/integrating) * [Tenant identification](/docs/v3/tenant-identification) #### Business * [SaaS boilerplate](/saas-boilerplate) * [Consulting](/contact) * [Audits](/contact) #### Links * [Branding](/branding) * [GitHub](https://github.com/stancl/tenancy) * [Discord](https://archte.ch/discord) * [Donate](/donate) #### Subscribe to our newsletter Receive notifications about important releases, new packages and other updates. Subscribe [Twitter](https://twitter.com/samuelstancl) [GitHub](https://github.com/stancl/tenancy) Made by [ArchTech](https://archte.ch) . © 2024 All rights reserved. Save time with our SaaS application template. Want to save time? Get our multi-tenant SaaS application template. [SaaS boilerplate](https://tenancyforlaravel.com/saas-boilerplate/) ---