Simplify your stack and build anything. Or everything.
Build tomorrow’s web with a modern solution you truly own.
Code-based nature means you can build on top of it to power anything.
It’s time to take back your content infrastructure.

Multi-Tenant Plugin

npm](https://img.shields.io/npm/v/@payloadcms/plugin-multi-tenants)

This plugin sets up multi-tenancy for your application from within your Admin Panel. It does so by adding a tenant field to all specified collections. Your front-end application can then query data by tenant. You must add the Tenants collection so you control what fields are available for each tenant.

Core features

  • Adds a tenant field to each specified collection
  • Adds a tenant selector to the admin panel, allowing you to switch between tenants
  • Filters list view results by selected tenant

Installation

Install the plugin using any JavaScript package manager like pnpm, npm, or Yarn:

1
pnpm add @payloadcms/plugin-multi-tenant@beta

Options

The plugin accepts an object with the following properties:

1
type MultiTenantPluginConfig<ConfigTypes = unknown> = {
2
/**
3
* After a tenant is deleted, the plugin will attempt to clean up related documents
4
* - removing documents with the tenant ID
5
* - removing the tenant from users
6
*
7
* @default true
8
*/
9
cleanupAfterTenantDelete?: boolean
10
/**
11
* Automatically
12
*/
13
collections: {
14
[key in CollectionSlug]?: {
15
/**
16
* Set to `true` if you want the collection to behave as a global
17
*
18
* @default false
19
*/
20
isGlobal?: boolean
21
/**
22
* Set to `false` if you want to manually apply the baseListFilter
23
*
24
* @default true
25
*/
26
useBaseListFilter?: boolean
27
/**
28
* Set to `false` if you want to handle collection access manually without the multi-tenant constraints applied
29
*
30
* @default true
31
*/
32
useTenantAccess?: boolean
33
}
34
}
35
/**
36
* Enables debug mode
37
* - Makes the tenant field visible in the admin UI within applicable collections
38
*
39
* @default false
40
*/
41
debug?: boolean
42
/**
43
* Enables the multi-tenant plugin
44
*
45
* @default true
46
*/
47
enabled?: boolean
48
/**
49
* Field configuration for the field added to all tenant enabled collections
50
*/
51
tenantField?: {
52
access?: RelationshipField['access']
53
/**
54
* The name of the field added to all tenant enabled collections
55
*
56
* @default 'tenant'
57
*/
58
name?: string
59
}
60
/**
61
* Field configuration for the field added to the users collection
62
*
63
* If `includeDefaultField` is `false`, you must include the field on your users collection manually
64
* This is useful if you want to customize the field or place the field in a specific location
65
*/
66
tenantsArrayField?:
67
| {
68
/**
69
* Access configuration for the array field
70
*/
71
arrayFieldAccess?: ArrayField['access']
72
/**
73
* When `includeDefaultField` is `true`, the field will be added to the users collection automatically
74
*/
75
includeDefaultField?: true
76
/**
77
* Additional fields to include on the tenants array field
78
*/
79
rowFields?: Field[]
80
/**
81
* Access configuration for the tenant field
82
*/
83
tenantFieldAccess?: RelationshipField['access']
84
}
85
| {
86
arrayFieldAccess?: never
87
/**
88
* When `includeDefaultField` is `false`, you must include the field on your users collection manually
89
*/
90
includeDefaultField?: false
91
rowFields?: never
92
tenantFieldAccess?: never
93
}
94
/**
95
* The slug for the tenant collection
96
*
97
* @default 'tenants'
98
*/
99
tenantsSlug?: string
100
/**
101
* Function that determines if a user has access to _all_ tenants
102
*
103
* Useful for super-admin type users
104
*/
105
userHasAccessToAllTenants?: (
106
user: ConfigTypes extends { user } ? ConfigTypes['user'] : User,
107
) => boolean
108
}

Basic Usage

In the plugins array of your Payload Config, call the plugin with options:

1
import { buildConfig } from 'payload'
2
import { multiTenantPlugin } from '@payloadcms/plugin-multi-tenant'
3
import type { Config } from './payload-types'
4
5
const config = buildConfig({
6
collections: [
7
{
8
slug: 'tenants',
9
admin: {
10
useAsTitle: 'name'
11
}
12
fields: [
13
// remember, you own these fields
14
// these are merely suggestions/examples
15
{
16
name: 'name',
17
type: 'text',
18
required: true,
19
},
20
{
21
name: 'slug',
22
type: 'text',
23
required: true,
24
},
25
{
26
name: 'domain',
27
type: 'text',
28
required: true,
29
}
30
],
31
},
32
],
33
plugins: [
34
multiTenantPlugin<Config>({
35
collections: {
36
pages: {},
37
navigation: {
38
isGlobal: true,
39
}
40
},
41
}),
42
],
43
})
44
45
export default config

Front end usage

The plugin scaffolds out everything you will need to separate data by tenant. You can use the tenant field to filter data from enabled collections in your front-end application.

In your frontend you can query and constrain data by tenant with the following:

1
const pagesBySlug = await payload.find({
2
collection: 'pages',
3
depth: 1,
4
draft: false,
5
limit: 1000,
6
overrideAccess: false,
7
where: {
8
// your constraint would depend on the
9
// fields you added to the tenants collection
10
// here we are assuming a slug field exists
11
// on the tenant collection, like in the example above
12
'tenant.slug': {
13
equals: 'gold',
14
},
15
},
16
})

NextJS rewrites

Using NextJS rewrites and this route structure /[tenantDomain]/[slug], we can rewrite routes specifically for domains requested:

1
async rewrites() {
2
return [
3
{
4
source: '/((?!admin|api)):path*',
5
destination: '/:tenantDomain/:path*',
6
has: [
7
{
8
type: 'host',
9
value: '(?<tenantDomain>.*)',
10
},
11
],
12
},
13
];
14
}

Examples

The Examples Directory also contains an official Multi-Tenant example.

Next

Stripe Plugin