<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Minhaj Haider Shah</title>
    <description>The latest articles on DEV Community by Minhaj Haider Shah (@dopescripts).</description>
    <link>https://dev.to/dopescripts</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4100034%2Ff796a7d1-ead0-44c0-8515-2ff2083a4875.jpg</url>
      <title>DEV Community: Minhaj Haider Shah</title>
      <link>https://dev.to/dopescripts</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dopescripts"/>
    <language>en</language>
    <item>
      <title>Laravel Multi-Tenancy for SaaS: Architecture and Code (2026 Guide)</title>
      <dc:creator>Minhaj Haider Shah</dc:creator>
      <pubDate>Sat, 29 Aug 2026 16:54:50 +0000</pubDate>
      <link>https://dev.to/dopescripts/laravel-multi-tenancy-for-saas-architecture-and-code-2026-guide-4hfg</link>
      <guid>https://dev.to/dopescripts/laravel-multi-tenancy-for-saas-architecture-and-code-2026-guide-4hfg</guid>
      <description>&lt;p&gt;Every Laravel SaaS hits the same fork in the road eventually: how do you keep Tenant A from ever seeing Tenant B's data? Get it wrong and you're not looking at a bug report , you're looking at a data breach. Get it right early, and tenancy becomes invisible infrastructure instead of a source of 2am incidents.&lt;/p&gt;

&lt;p&gt;This guide walks through the real architectural decision , single database vs. multi-database vs. hybrid , and shows how to implement it with Spatie's &lt;code&gt;laravel-multitenancy&lt;/code&gt; package, with copy-pasteable code throughout.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Your First Decision Is Rarely Your Final One&lt;/li&gt;
&lt;li&gt;1. Architectural Patterns&lt;/li&gt;
&lt;li&gt;2. Spatie's laravel-multitenancy Package&lt;/li&gt;
&lt;li&gt;3. Database Setup and Migrations&lt;/li&gt;
&lt;li&gt;4. Writing Tenant-Aware Code&lt;/li&gt;
&lt;li&gt;5. Pitfalls &amp;amp; Tips&lt;/li&gt;
&lt;li&gt;Laravel Multi-Tenancy Checklist&lt;/li&gt;
&lt;li&gt;FAQ&lt;/li&gt;
&lt;li&gt;Resources&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Your First Decision Is Rarely Your Final One
&lt;/h2&gt;

&lt;p&gt;Most SaaS architects start with a single, shared database because it's the fastest way to ship. That's usually the right call , but as customer count, compliance requirements, or per-tenant data volume grow, many teams end up migrating toward isolated or hybrid setups. Knowing that migration path exists &lt;em&gt;before&lt;/em&gt; you need it changes how you design your schema from day one.&lt;/p&gt;

&lt;p&gt;The practical trend in 2026 is a &lt;strong&gt;hybrid model&lt;/strong&gt;: a shared global database for cross-tenant concerns (billing, auth, platform admin) paired with isolated per-tenant databases for the actual application data. It gives you the operational simplicity of a single control plane with the hard data isolation of separate databases where it matters most.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Architectural Patterns
&lt;/h2&gt;

&lt;p&gt;For more details checkout our detailed article on this topic &lt;a href="https://dev.to/blog/laravel-single-db-vs-database-per-tenant"&gt;Laravel single vs Multi-Database&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Single Database (Shared Schema)
&lt;/h3&gt;

&lt;p&gt;Every tenant's rows live in the same tables, scoped by a &lt;code&gt;tenant_id&lt;/code&gt; column.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simplest to build, migrate, and deploy&lt;/li&gt;
&lt;li&gt;One connection pool, one set of migrations&lt;/li&gt;
&lt;li&gt;Cheapest to run at small-to-medium scale&lt;/li&gt;
&lt;li&gt;Easy cross-tenant analytics and admin tooling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A missing &lt;code&gt;tenant_id&lt;/code&gt; scope is a data-leak waiting to happen&lt;/li&gt;
&lt;li&gt;Noisy-neighbor risk , one tenant's heavy query can slow down everyone&lt;/li&gt;
&lt;li&gt;Harder to satisfy enterprise customers who require physical data isolation&lt;/li&gt;
&lt;li&gt;Backup/restore for a single tenant means filtering, not just restoring a file
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌───────────────────────────────┐
│         app_database          │
│  ┌──────────────────────────┐ │
│  │ users (tenant_id: 1,2)   │ │
│  │ orders (tenant_id: 1,2)  │ │
│  │ invoices (tenant_id: 1,2)│ │
│  └──────────────────────────┘ │
└───────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Multi-Database (Isolated Tenants)
&lt;/h3&gt;

&lt;p&gt;Each tenant gets their own database (or schema). The application switches connections at runtime based on who's logged in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;True data isolation , no shared tables means no leak risk from a forgotten &lt;code&gt;WHERE&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Per-tenant backup, restore, and even geographic placement&lt;/li&gt;
&lt;li&gt;Easier to satisfy enterprise/compliance requirements (SOC 2, HIPAA, data residency)&lt;/li&gt;
&lt;li&gt;One tenant's load doesn't degrade another's queries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Migrations must run against every tenant database&lt;/li&gt;
&lt;li&gt;Connection management adds real complexity&lt;/li&gt;
&lt;li&gt;Cross-tenant reporting requires aggregating across databases&lt;/li&gt;
&lt;li&gt;More expensive at scale (more connections, more overhead per tenant)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌───────────┐  ┌───────────┐  ┌───────────┐
│  tenant_a │  │  tenant_b │  │  tenant_c │
│  (own DB) │  │  (own DB) │  │  (own DB) │
└───────────┘  └───────────┘  └───────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Hybrid (Shared Global + Per-Tenant Databases)
&lt;/h3&gt;

&lt;p&gt;A &lt;code&gt;landlord&lt;/code&gt; database holds tenant records, billing, and platform-wide auth. Each tenant then gets an isolated database for their actual application data.&lt;/p&gt;

&lt;p&gt;This pattern is increasingly the default for growing Laravel SaaS products in 2026 , it keeps platform operations (like tenant provisioning and billing) simple while still giving each tenant hard data isolation for the data that actually matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Spatie's laravel-multitenancy Package
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/spatie/laravel-multitenancy" rel="noopener noreferrer"&gt;&lt;code&gt;spatie/laravel-multitenancy&lt;/code&gt;&lt;/a&gt; is the go-to solution for implementing this pattern in Laravel without hand-rolling connection-switching logic yourself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require spatie/laravel-multitenancy

php artisan vendor:publish &lt;span class="nt"&gt;--tag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"multitenancy-config"&lt;/span&gt;
php artisan vendor:publish &lt;span class="nt"&gt;--tag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"multitenancy-migrations"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configuring tenant detection
&lt;/h3&gt;

&lt;p&gt;The package supports detecting the current tenant by domain, subdomain, or a custom strategy. Domain-based detection is the most common approach for B2B SaaS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// config/multitenancy.php&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Spatie\Multitenancy\TenantFinder\DomainTenantFinder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'tenant_finder'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;DomainTenantFinder&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="s1"&gt;'tenant_model'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;\App\Models\Tenant&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="s1"&gt;'switch_tenant_tasks'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;\Spatie\Multitenancy\Tasks\SwitchTenantDatabaseTask&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;\Spatie\Multitenancy\Tasks\PrefixCacheTask&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The tenant model
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/Models/Tenant.php&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Spatie\Multitenancy\Models\Tenant&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nc"&gt;BaseTenant&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Tenant&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BaseTenant&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$fillable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'domain'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'database'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Middleware: switching the connection per request
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// bootstrap/app.php or routes/web.php&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Spatie\Multitenancy\Http\Middleware\NeedsTenant&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Spatie\Multitenancy\Http\Middleware\EnsureValidTenantSession&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nc"&gt;NeedsTenant&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;EnsureValidTenantSession&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/dashboard'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;DashboardController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once &lt;code&gt;NeedsTenant&lt;/code&gt; resolves the tenant from the request domain, every subsequent Eloquent query in that request automatically hits the correct tenant database , no manual scoping required.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Database Setup and Migrations
&lt;/h2&gt;

&lt;p&gt;Split your migrations into two directories: &lt;code&gt;database/migrations&lt;/code&gt; for the landlord (global) database, and &lt;code&gt;database/migrations/tenant&lt;/code&gt; for anything that belongs inside each tenant database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// config/multitenancy.php&lt;/span&gt;

&lt;span class="s1"&gt;'migrate_tenant_migration_path'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;database_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'migrations/tenant'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Running migrations across all tenants
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan tenants:artisan &lt;span class="s2"&gt;"migrate --path=database/migrations/tenant"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Creating a tenant programmatically
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Models\Tenant&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$tenant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Tenant&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Acme Corp'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'domain'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'acme.yourapp.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'database'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'tenant_acme'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="nv"&gt;$tenant&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;makeCurrent&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$tenant&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;createDatabase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nc"&gt;Artisan&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'migrate'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'--database'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'tenant'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'--path'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'database/migrations/tenant'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'--force'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Global vs. tenant-specific tables
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Table&lt;/th&gt;
&lt;th&gt;Location&lt;/th&gt;
&lt;th&gt;Reasoning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tenants&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Landlord DB&lt;/td&gt;
&lt;td&gt;Registry of all tenants , must be queryable without a tenant context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;subscriptions&lt;/code&gt; / &lt;code&gt;plans&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Landlord DB&lt;/td&gt;
&lt;td&gt;Billing spans the platform, not a single tenant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;users&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tenant DB&lt;/td&gt;
&lt;td&gt;Each tenant's users are isolated from every other tenant's&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;orders&lt;/code&gt;, &lt;code&gt;invoices&lt;/code&gt;, &lt;code&gt;products&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Tenant DB&lt;/td&gt;
&lt;td&gt;Core application data , the whole reason isolation exists&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  4. Writing Tenant-Aware Code
&lt;/h2&gt;

&lt;p&gt;Once the middleware has switched the connection, ordinary Eloquent queries are automatically scoped to the current tenant , no extra &lt;code&gt;where()&lt;/code&gt; clause needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Inside a request that has already resolved a tenant via middleware&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Controller&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Only returns users belonging to the current tenant's database&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Creating a tenant at registration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RegisterTenantController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Controller&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$tenant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Tenant&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;company_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'domain'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Str&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;company_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'.yourapp.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'database'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'tenant_'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nc"&gt;Str&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;company_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'_'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="nv"&gt;$tenant&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;makeCurrent&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nv"&gt;$tenant&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;createDatabase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nc"&gt;Artisan&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'migrate'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'--database'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'tenant'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'--path'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'database/migrations/tenant'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'--force'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;admin_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;admin_email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'password'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Hash&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"https://&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$tenant&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/dashboard"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Pitfalls &amp;amp; Tips
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Missing tenant scope is the #1 data-leak risk.&lt;/strong&gt; In a single-DB setup, one forgotten &lt;code&gt;tenant_id&lt;/code&gt; filter on a query , especially a raw query or a job running outside the request lifecycle , can leak one tenant's data to another. Always scope explicitly, or rely on global scopes/traits that enforce it automatically.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Unindexed tenant queries get slow fast.&lt;/strong&gt; Every query in a shared-schema setup effectively filters by &lt;code&gt;tenant_id&lt;/code&gt;, so it needs to be the leading column in your indexes. A composite index like &lt;code&gt;(tenant_id, created_at)&lt;/code&gt; will outperform a plain index on &lt;code&gt;created_at&lt;/code&gt; alone once you have more than a handful of tenants.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Queued jobs lose tenant context by default.&lt;/strong&gt; Since jobs run outside the HTTP request lifecycle, the middleware that sets the current tenant never fires. Spatie's package provides tenant-aware job dispatching , use it, or you'll get jobs silently running against the wrong (or no) tenant database.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cache keys need tenant prefixing.&lt;/strong&gt; Without it, a cached value for Tenant A can be served to Tenant B. The &lt;code&gt;PrefixCacheTask&lt;/code&gt; in the config above handles this automatically , don't skip it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Test tenant isolation explicitly.&lt;/strong&gt; Write a test that creates two tenants, seeds different data into each, and asserts that switching context returns only the expected tenant's rows. This is the single highest-leverage test in a multi-tenant codebase.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Laravel Multi-Tenancy Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Chosen an architecture (single-DB, multi-DB, or hybrid) based on actual compliance/scale needs , not by default&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;spatie/laravel-multitenancy&lt;/code&gt; installed and tenant finder configured&lt;/li&gt;
&lt;li&gt;[ ] Landlord vs. tenant migrations split into separate directories&lt;/li&gt;
&lt;li&gt;[ ] Every tenant-scoped table has &lt;code&gt;tenant_id&lt;/code&gt; as the leading index column (single-DB) or lives in an isolated database (multi-DB)&lt;/li&gt;
&lt;li&gt;[ ] Middleware (&lt;code&gt;NeedsTenant&lt;/code&gt;) applied to every tenant-facing route&lt;/li&gt;
&lt;li&gt;[ ] Cache keys are tenant-prefixed&lt;/li&gt;
&lt;li&gt;[ ] Queued jobs explicitly carry and restore tenant context&lt;/li&gt;
&lt;li&gt;[ ] A test exists that verifies tenant data isolation end-to-end&lt;/li&gt;
&lt;li&gt;[ ] Tenant provisioning (creation, database setup, migration run) is scripted, not manual&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Should I start with single-DB or multi-DB for a new SaaS?&lt;/strong&gt;&lt;br&gt;
Start with single-DB unless you already know you need hard isolation (e.g., an enterprise customer requiring it contractually). It's faster to build and sufficient for most early-stage products. Design your schema so &lt;code&gt;tenant_id&lt;/code&gt; is present everywhere from day one , that's what makes a later migration to multi-DB tractable instead of a rewrite.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does Spatie's package support subdomain-based tenant detection?&lt;/strong&gt;&lt;br&gt;
Yes , &lt;code&gt;DomainTenantFinder&lt;/code&gt; works with both full custom domains and subdomains. You can also write a custom tenant finder if you need to resolve tenants by something other than the request domain, like a header or path segment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I handle a tenant that outgrows shared infrastructure?&lt;/strong&gt;&lt;br&gt;
This is exactly what the hybrid model is for. Keep the tenant's record and billing in the landlord database, but provision them their own isolated tenant database. Since your application code is already tenant-aware, this becomes an infrastructure change rather than an application rewrite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://spatie.be/docs/laravel-multitenancy/v3/introduction" rel="noopener noreferrer"&gt;Spatie , laravel-multitenancy Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://laravel.com/docs/database" rel="noopener noreferrer"&gt;Laravel Docs , Database Configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://laravel.com/docs/queues" rel="noopener noreferrer"&gt;Laravel Docs , Queues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/spatie/laravel-multitenancy" rel="noopener noreferrer"&gt;Spatie , laravel-multitenancy on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>discuss</category>
    </item>
  </channel>
</rss>
