How To Build A Multi-Tenant App With Payload

Published On
Use a multi-tenant app to serve the same application on several different domains
Use a multi-tenant app to serve the same application on several different domains
Cut costs, save time, and ship faster by sharing infrastructure when you setup Payload as a multi-tenant application.

If you have ever built or shipped the same application twice, then you likely fit into one of these two scenarios:

  1. You search for an old repository, then duplicate and modify it. Hopefully you chose a repo with all your latest and greatest features.
  2. You have a single repository that you share across all of your projects. You use environment variables to control the instance (great) but you still deploy them independently of one another.

Then when you go to deploy your project you do one of the following:

  1. You spin up a fresh server for your new project (and pay for it). You configure the operating system, create the database, secure the network...the works.
  2. You have an existing server where you log in and clone down your new repo alongside the others. You use a reverse proxy to host each instance independently of one another (great) but you still need to coordinate deployments.

With multi-tenancy, you maintain a single repository and host your application on a single server, eliminating the need for separate repositories and deployments. The infrastructure and codebase is shared across all of your tenants, the cost saving is passed onto you, and any new features you build apply across all of your tenants simultaneously. Tenants can even use custom domains and might not ever even be made aware of their tenancy. Each tenant has its own set of users, pages, and other data. A tenant for you might be:

  1. Your agency's client
  2. Your SaaS customers
  3. Your business's organizations
Users under one tenant will only see that tenant's pages.

If you are an agency that builds websites, for example, you have likely built essentially the same CMS for every one of your clients. This means that when Client A asks for a new feature, you build it into Client A's project then ship it out. If Client B asks for the same feature, you copy and paste your code into Client B's project then ship it out to them. You later find a bug in your new feature, so you patch it for both Client A and Client B then also ship it to each of them. If you have more than two clients, you can see how unwieldy this might get.

Now imagine your agency just landed 2 more clients. For each of these two, you stand up a fresh server complete with its own hardware, database, firewall protections, etc. Or maybe you are like me and you do share a single server and database but still run separate Payload instances. The same issues apply, maintaining multiple codebases or deployments is hard. It takes a lot of work, coordination, and is error prone unless you go through the trouble of setting up some fancy CI.

Setting up a multi-tenant application with Payload will unlock your engineering bandwidth and with Payload's Access Control and Hooks this is trivial.

How It Works

Access Control

By far the biggest hurdle of any multi-tenant application is data isolation. Tenants must only have access to the data within their tenancy. To achieve this in Payload we set up a simple "role-based access control". This will allow us to scope data to only the roles and tenants that the user is a member of.

Super Admin

On every user there is a roles field. These are global administrative roles and can be whatever roles might apply to your application. The super admin role is a user with access to the entire application, including all tenants. Super admins can log into the admin panel and perform any operation. They see all tenants and can even browse the application as a specific tenant. Super admins can manage all users, create new tenants, and everything in between.

With a super admin role, users have access to every tenant.

Tenant Admin

Users are also assigned a `tenants` array, where they are given membership to each tenant along with their roles within that tenant. A tenant admin is a user with limited access to only the tenants that are assigned to them. Tenant admins can create new users and documents within their tenant but have access to nothing outside. Users can be associated with more than one tenant and have different roles for each.

Tenant admins manage only the tenant they are assigned.

Domains

An extremely powerful feature of a Payload multi-tenant application is the ability to serve the same application on different domains. This means that your tenants can browse the admin panel on their own unique domain and might not ever be made aware of their tenancy. This means that Client A might login to client-a.com/admin while Client B uses client-b.com/admin. To do this in Payload we assign each tenant a domains array. Then when a user logs in, we match their current req.headers.origin with one listed in their tenants, and if found, scope their access to that tenant. Admins would still be able to log in using one central domain, like main-app.com/admin.

Clients have their own domain and are unaware of their multi-tenancy.

Front-end

If you're building a website or other front-end for your tenant, all you need to do is specify the tenant as a query parameter in your requests. For example, if you wanted to fetch all pages for the tenant ABC, you would make a request to /api/pages?where[tenant][slug][equals]=abc. Payload's authentication will ensure that the user making the request has proper access to the resource.

Demo

To try this out yourself, check out the official Payload Multi-Tenant Example. This is a fully working Payload app that will get you up and running immediately with everything discussed here. For a head start on building a website for your tenant(s), check out the official Website Template. It includes a page layout builder, preview, SEO, and much more. It is not multi-tenant, though, but you can easily take the concepts from that example and apply them here.

Learn More

To learn more about Payload and the features used here, take a look at the following resources: