Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

noblemfd's avatar

Single DB Multi-tenancy without using Package

I am trying to develop a multi-tenancy application without using Package in Laravel-5.8.

I have gone through the multi-tenancy videos in Laracast, but some things were not treated. I am using traits and scopes

Models

class Company extends Model
{
    protected $table = 'companies';

    protected $fillable = [
       'id',
       'company_code',
       'company_name'
   ];
}

class User extends Authenticatable
{
  protected $fillable = [
   'name',
   'company_id',
   'email',
];

}

The main domain is:

https://myapp.com

Sub domains are:

https://company1.myapp.com

https://company2.myapp.com

I using socialite for login authentication. For the main company:

config/services.php

'azure' => [
   'client_id' => env('AZURE_KEY','23333-ff333-9103-fgghhh-cc8b955ed23c'),
   'client_secret' => env('AZURE_SECRET','ffjjfnfhfhhhhff'),
   'redirect' => env('AZURE_REDIRECT_URI','https://myapp.com/login/azure/callback')
],

config/mail.php

 return [
   'driver' => env('MAIL_DRIVER', 'smtp'),
   'host' => env('MAIL_HOST', 'smtp.office365.com'),
   'port' => env('MAIL_PORT', 587),

   'from' => [
       'address' => env('MAIL_FROM_ADDRESS', '[email protected]'),
       'name' => env('MAIL_FROM_NAME', 'COMPANY1'),
   ],

   'encryption' => env('MAIL_ENCRYPTION', 'tls'),
   'username' => env('MAIL_USERNAME','[email protected]'),
   'password' => env('MAIL_PASSWORD','maincompany'),    
   'sendmail' => '/usr/sbin/sendmail -bs',

   'log_channel' => env('MAIL_LOG_CHANNEL'),
];

How do I treat:

config/services.php and config/mail.php

for the three companies https://myapp.com, https://company1.myapp.com and https://company2.myapp.com

Thanks

0 likes
6 replies
martinbean's avatar

@noblemfd I’ve a couple of single database, multi-tenancy applications running in production that have been for a few years now, and were built without packages.

If you’re planning on adding a trait/scope to models to automatically scope them to a tenant, as someone who has been there and done that, my advise is: don’t. I took that approach in the first version of one of my apps, and it ended up causing more problems than it solved. You can use your models in admin panels, Artisan commands, queued jobs etc without having to also do something like call withoutGlobalScopes all over the place, which just let to unmanageable and unmaintainable code, so I ended up rewriting the app without global scopes and things are much, much better.

In terms of your tenant-specific settings, you’ll need to store them in a database rather than configuration files if you’re wanting tenants to be able to set their own mail settings etc. You can then use middleware to load the relevant settings from the database:

class LoadTenantMailSettings
{
    public function handle(Request $request, Closure $next)
    {
        // Identify tenant from subdomain
        // Load settings for that tenant

        config('mailers.tenant', [
                // Tenant-specific mail settings here
        ]);

        return $next($request);
    }
}

With the mailer configured at runtime, you can then use that in your application:

Mail::mailer('tenant')->to('[email protected]')->send(new SomeTenantMailable());
noblemfd's avatar

Thanks @martinbean. How do I make tenants to do their setting, and how do I call it.

$mail=DB::table('mail_settings')->first();
$config = array(
       'driver' => $mail->driver,
       'host' => $mail->host,
       'port' => $mail->port,
       'from' => array('address' => $mail->from_address, 'name' => $mail->from_name),
       'encryption' => $mail->encryption,
       'username' => $mail->username,
       'password' => $mail->password,
       'sendmail' => '/usr/sbin/sendmail -bs',
       'pretend' => false
      );
 Config::set('mail',$config);

Then, how about the Socialite Login and redirect

Thanks

Dhruva's avatar

Hi.. I am creating a single sb multi tenant. Following practices used in course 'multitenancy in laravel'. But your suggestions are different and not using scope/trait.

Please guide me, how this will create issue in long term if I use globalscope and traits for tenants.

martinbean's avatar

@thomasvantuycom I use route–model binding to look up the website (tenant) by hostname, and then use that model as a “root” to access data via relations:

Route::domain('{website:domain}')->group(function () {
    // Website-specific routes
    Route::get('articles/{article:slug}', [ArticleController::class, 'show']);
})->where([
    'website' => '.*',
]);
class ArticleController extends Controller
{
    public function show(Website $website, Article $article)
    {
        return view('website::article.show', compact('website', 'article'));
    }
}

Because I specify a column in the {article} binding, Laravel will “scope” it and check it actually belongs to the website on the request as well: https://laravel.com/docs/9.x/routing#implicit-model-binding-scoping

This means that I can’t access articles belonging to Website 1 via Website 2.

GET website1.test/articles/website-one-article // 200 OK
GET website2.test/articles/website-one-article // 404 Not Found
martinbean's avatar

@noblemfd That’s your business logic and for you to decide I’m afraid. The simplest method is to just have a model and a section in your application’s control panel for tenants to input their own settings.

noblemfd's avatar

@martinbean - Can you guide me through the best practice or any link? I've been on it for some days now.

Please or to participate in this conversation.