.jpg&w=3840&q=75)
.jpg&w=3840&q=75)
Awhile back, I put together a couple of examples showing how to build multi-tenant apps with Payload:
/tenant-a/pagetenant-a.comBoth use Payload’s Multi-Tenant Plugin, but the biggest confusion I’ve seen isn’t installing the plugin—it’s understanding how everything fits together between the backend and the frontend.
This post walks through two approaches and, more importantly, explains the missing piece most people run into.
Payload’s Multi-Tenant Plugin provides the infrastructure for tenant isolation, not frontend routing.
Out of the box, it helps you:
But it does not:
Start with the official plugin:
https://payloadcms.com/docs/plugins/multi-tenant
A minimal config might look like:
You’ll also need a tenants collection and to enable the plugin on any collection you want scoped.
Note: if you're using a custom collection name for your tenants (e.g. brands instead of tenants), pass tenantsSlug to the plugin:
At this point, your Payload configuration is tenant-aware, but your frontend still does not know which tenant corresponds to the incoming URL.
This is where most people get stuck. Before you query Payload, you need to answer: “Which tenant is this request for?” This happens in your application’s routing layer—for example, in a route segment, layout, Next.js rewrite, middleware, or Proxy.
There are two common approaches:
Path-Based Routing
Example repo:
https://github.com/zubricks/path-based-multi-tenant
How it works
Your URL includes the tenant:
app.com/tenant-a/page
In Next.js, you might have:
Only include user when you actually have an authenticated Payload user. For public pages, overrideAccess: false applies your collection’s unauthenticated access rules, while the explicit tenant condition determines which tenant’s page is returned.
A good pattern is to validate the tenant exists in a layout component that wraps all [tenant] routes, so you don't repeat that check in every page.
Path based routing is simple to implement, does not require domain setup and works well locally for development and test.
Example repo:
https://github.com/zubricks/multi-tenant-example
How it works
Each tenant has its own domain:
tenant-a.com
tenant-b.com
One approach—and the one shown in Payload’s official documentation—is to use Next.js rewrites in next.config.js. The rewrite captures the hostname and passes it to a [tenantDomain] route segment.
With this in place, a request to tenant-a.example.com/about is internally rewritten to /tenant-a.example.com/about, while the browser URL remains unchanged. Your [tenantDomain] segment receives the full hostname as its value.
Your layout or page then queries by the domain field on the tenant record:
And when filtering content, you query by domain through the relationship:
“My data isn’t filtering” | Confirm that the frontend query includes an explicit tenant condition. If you expect Payload’s access control to provide additional filtering, remember that Local API calls bypass access control unless you set overrideAccess: false. | |
“All tenants see the same data” | Confirm that the collection is included in the plugin configuration, the query is constrained to the resolved tenant, and access control is enabled when the request depends on the current user’s tenant permissions. | |
“Frontend doesn’t match backend” | You’re resolving tenant one way (e.g. domain), but querying another way. | |
“It works locally but not in production” | This is often caused by domain, DNS, SSL certificate, hostname normalization, or rewrite-matching differences in production. |
The Multi-Tenant Plugin provides the CMS infrastructure for tenant isolation, including tenant relationships, Admin filtering, and tenant-aware access constraints.
But a complete multi-tenant app requires:
If you keep that separation in mind, everything becomes much easier to reason about.