Multitenancy is one of those topics you usually discover too late. You start a B2B SaaS application, you launch it, it takes off, and one day you realize you’ve accumulated the data of 200 customers in the same tenant_confidential_stuff table, with a client_id column that you must never, ever forget in the WHERE clause.
At that point, it’s still possible to do things properly, but the cost has nothing in common anymore with that of a decision made at the start.
This series takes on the subject with a clear stance: multitenancy is not a pattern you slap onto an existing app, it’s an architecture decision that gets made very early, and that then plays out at several levels (database, cache, files, queues). We’ll walk through these levels one by one, in the context of a concrete Laravel application.
This first article sets the scene: what is a tenant, what are we trying to isolate, and above all, should you go down this road, or is it a problem that simply doesn’t concern you?
These articles are the written version of the talk I gave at Forum PHP 2025. You can find the video on the AFUP YouTube channel, the association that keeps the French-speaking PHP community alive (afup.org).
The short definition
Multitenancy is a software architecture where a single application instance serves multiple customers in an isolated way. These customers are called tenants, in the sense that they rent the infrastructure you own. The application is shared, but each tenant’s data stays sealed off: a tenant never sees another tenant’s data, and ideally, can’t even tell that other tenants exist.
This is what separates a B2B SaaS from an on-premise deployment: with multitenancy, you don’t ship a copy of your application to each customer, you serve them all the same one, guaranteeing isolation by construction, not by deployment.
There are two main goals behind this approach:
Security by construction. Data isolation must not depend on a developer not having forgotten a WHERE tenant_id = ? in a query. If isolation relies on human discipline, it will eventually break. The goal of a good multi-tenant architecture is to make it impossible (or at least very hard) to write a query that leaks data between tenants.
Performance and scalability. Being able to grow each tenant independently, isolating resources so that one huge customer doesn’t degrade the service for everyone else, applying different optimization strategies depending on usage profiles.
If neither of these two goals concerns you, ask yourself frankly whether the complexity of a multi-tenant architecture is worth it.
What constitutes your tenant
This is the first real decision, and the one that usually gets made too quickly. A tenant is the entity whose data you want to isolate, and, by construction, the entity for which you want to make sharing harder with any other entity.
A few classic candidates:
- The customer organization. This is the most common case in B2B. Every company that signs up to your application is a tenant. Everything it creates (internal users, projects, invoices, content) belongs to it and never leaves its perimeter.
- The team or department. You want two teams within the same company to be invisible to each other. This is typical of HR tools or project management platforms where internal compartmentalization is part of the promise.
- The user. Rarer as a primary tenant, but it can happen in products where every user is an island, a personal note-taking tool, for example. Careful: if tomorrow you want to allow sharing and interaction between users, this choice becomes a trap.
When in doubt, pick the highest separation in the hierarchy. Starting with an “organization” tenant and later needing to compartmentalize by team inside it is code to add, not a rewrite. Starting with a “team” tenant and later wanting to share things between teams of the same company is much more painful.
There is also a distinction that’s easy to blur: the tenant’s identifier and the identifier of the business object it represents. Your tenant may be tied to an Organization on the business side, but its internal identifier (the one that will end up in schema names, cache prefixes, S3 buckets) is better off being independent, typically a dedicated UUID. You’ll be glad you did when the time comes to rename an organization, merge two customers, or handle a free trial before it gets attached to a permanent account.
Multitenancy or not, deciding honestly
We’ll skip the classic trap of comparison tables. The real question is simpler: what is the cost of a data leak between two customers?
If it’s very high (B2B, sensitive data, regulations like GDPR, certifications like ISO 27001), then you want strong isolation, and multitenancy is the main tool for it. If it’s low or nonexistent (B2C with public data, or non-sensitive content), you’re probably complicating things for nothing.
The cases where multitenancy clearly makes sense:
- A B2B SaaS application with several corporate customers.
- Sensitive data to isolate per customer (HR, health, finance, contracts).
- A need to grow part of the infrastructure independently for some tenants (one huge customer must not degrade the others).
- Regulatory requirements on data location or isolation.
The cases where it’s probably over-engineering:
- You have a single customer, and no others are planned. Pointless.
- The data is public and shared between users. A general-public blogging platform has no reason to isolate by tenant.
- You’re in B2C with very little need for isolation. The user can be a logical tenant, but you don’t need the full machinery.
One in-between case deserves a mention: B2B2C applications, where you have corporate customers (tenants) that have their own end users. There, multitenancy is almost always the right call: your tenants are the companies, and you manage the users inside each tenant.
Why now, and not later
The cost of putting multitenancy in place depends very heavily on when you get to it.
At the start of the project, a few hours of setup. You write your first migrations knowing they’ll live in a per-tenant schema (or with a tenant_id column, or in a dedicated database), and everything else naturally falls in line.
Early in production, a few days to a few weeks. You already have data, but few tenants, and the code isn’t too scattered yet. A migration is needed, but the scope stays manageable and the operation is easy to carry out.
In mature production, months, possibly a project of its own. You have hundreds (or thousands) of customers, legacy code, external integrations that depend on the current structure, incremental backups that won’t adapt on their own. At that point, it’s no longer a refactoring: it’s a project that ties up a full team for several quarters.
The takeaway is short: if you think you’ll ever need multitenancy, put it in now. Even a simple implementation (say, a tenant_id column and a global scope) will spare you an enormous amount of pain later, and will leave you the option of migrating to a stricter strategy (schemas, dedicated databases) the day you need it.
Changing the isolation strategy inside an application that is already multi-tenant is a feasible project. Turning a non multi-tenant application into a multi-tenant one is a whole other level of difficulty.
What comes next
This series will apply multitenancy to every compartment of a typical Laravel application. The angle stays practical: no theoretical catalog of possible architectures, but concrete strategies, their operational cost, and the code that goes with them.
On the program:
- This article: the scene, the definition, identifying your tenant, the yes/no decision
- Database isolation strategies: column, schema, dedicated database; the details of PostgreSQL (RLS, schemas); a word on MariaDB and MongoDB
- Cache and files: prefixes, tags, dedicated buckets
- The Laravel implementation: Spatie vs Stancl, the Tenant model, detection, switching between tenants
- Going further: commands, queues, default tenant, migrations on creation, separating active and inactive tenants
The goal is not to tick every possible box. It’s to give you enough material to make informed choices, and to share the trade-offs that have aged well in real projects.