Imagine you are building a project management tool. You want to sell it to many companies. Each company should only see their own projects, their own users, their own data. That is multi-tenancy — one application, many customers, completely separated data.
When I first built a multi-tenant app, I made it more complicated than it needed to be. Let me save you that trouble.
The three ways to do it
There are three main approaches, and the right one depends on your situation.
Shared database, shared tables is the simplest. Every row in your database has a tenant_id column. When you query data, you always filter by the current tenant. This works well for most SaaS apps and is the easiest to build and maintain.
The danger here is forgetting to filter. If you accidentally show one customer's data to another, that is a serious problem. You can prevent this with Eloquent global scopes — they automatically add the tenant filter to every query.
Shared database, separate schemas gives each tenant their own set of tables inside the same database. PostgreSQL handles this well. It is more isolated than shared tables but does not cost as much as separate databases.
Separate database per tenant gives the strongest isolation. Each customer gets their own database. This is great for enterprise customers who need data sovereignty. The downside is higher infrastructure cost and more complex migrations.
How do you know which tenant is making the request?
The most common way is subdomains. acme.yourapp.com maps to the Acme tenant. globex.yourapp.com maps to Globex.
You resolve the tenant in middleware, early in the request lifecycle, and store it somewhere accessible — usually in a service container binding or a static property.
The package that does the heavy lifting
stancl/tenancy is the go-to package for Laravel multi-tenancy. It handles tenant resolution, database switching, and a lot of the boilerplate. I recommend starting with it rather than building everything from scratch.
The things that trip people up
Tenant isolation is easy to get wrong in subtle ways. Here are the mistakes I have seen:
Forgetting to scope file storage. If you store uploaded files, make sure each tenant's files are in a separate folder or bucket.
Background jobs losing tenant context. When a queued job runs, it needs to know which tenant it belongs to. Make sure you pass the tenant ID with the job and restore the context when it runs.
Caching across tenants. If you cache a query result, make sure the cache key includes the tenant ID. Otherwise tenant A might see tenant B's cached data.
Start simple
My advice: start with the shared database approach. It is simpler to build, simpler to maintain, and handles most use cases well. You can always migrate to separate databases later if a big enterprise customer requires it.
Do not over-engineer this upfront. Get your product working first, then add complexity only when you actually need it.




