158 lines
5.1 KiB
Markdown
158 lines
5.1 KiB
Markdown
---
|
|
title: "Multi-Tenant Plugin"
|
|
aliases: [multi-tenancy, plugin-multi-tenant, tenant-isolation]
|
|
tags: [payloadcms, plugins, multi-tenant, access-control]
|
|
sources: [raw/plugins__multi-tenant.md]
|
|
created: 2026-05-15
|
|
updated: 2026-05-15
|
|
---
|
|
|
|
## Overview
|
|
|
|
`@payloadcms/plugin-multi-tenant` scaffolds full multi-tenancy inside a single Payload instance. It adds a `tenant` relationship field to every specified collection, injects a tenant selector into the Admin Panel, and filters list views + relationship pickers to the active tenant.
|
|
|
|
## Core Features
|
|
|
|
- Adds `tenant` field to each opted-in collection
|
|
- Admin Panel tenant selector — switches context, filters all list views
|
|
- Filters relationship fields by active tenant
|
|
- **Global-like collections** — `isGlobal: true` enforces 1 doc per tenant
|
|
- Auto-assigns tenant to new documents
|
|
- Cleans up documents when a tenant is deleted (`cleanupAfterTenantDelete`, default `true`)
|
|
|
|
> **Warning:** Tenant deletion cascades to all related documents by default. Apply strong access control on the tenants collection or set `cleanupAfterTenantDelete: false`.
|
|
|
|
## Installation
|
|
|
|
```bash
|
|
pnpm add @payloadcms/plugin-multi-tenant
|
|
```
|
|
|
|
## Basic Config
|
|
|
|
```ts
|
|
import { multiTenantPlugin } from '@payloadcms/plugin-multi-tenant'
|
|
import type { Config } from './payload-types'
|
|
|
|
export default buildConfig({
|
|
collections: [
|
|
{
|
|
slug: 'tenants',
|
|
admin: { useAsTitle: 'name' },
|
|
fields: [
|
|
{ name: 'name', type: 'text', required: true },
|
|
{ name: 'slug', type: 'text', required: true },
|
|
{ name: 'domain', type: 'text', required: true },
|
|
],
|
|
},
|
|
],
|
|
plugins: [
|
|
multiTenantPlugin<Config>({
|
|
collections: {
|
|
pages: {},
|
|
navigation: { isGlobal: true }, // 1 doc per tenant
|
|
},
|
|
}),
|
|
],
|
|
})
|
|
```
|
|
|
|
You own the `tenants` collection — add any fields you need (slug, domain, plan, etc.).
|
|
|
|
## Key Options
|
|
|
|
| Option | Default | Purpose |
|
|
|--------|---------|---------|
|
|
| `collections` | required | Map of collection slugs to per-collection options |
|
|
| `tenantsSlug` | `'tenants'` | Slug for the tenants collection |
|
|
| `cleanupAfterTenantDelete` | `true` | Remove tenant's docs on deletion |
|
|
| `userHasAccessToAllTenants` | — | Function for super-admin bypass |
|
|
| `tenantsArrayField.includeDefaultField` | `true` | Auto-add tenants array to users |
|
|
| `useBaseFilter` | `true` | Filter list view + relationships by tenant |
|
|
| `useTenantAccess` | `true` | Apply access constraints per tenant |
|
|
| `debug` | `false` | Show tenant field in Admin UI |
|
|
|
|
### Per-Collection Options
|
|
|
|
```ts
|
|
collections: {
|
|
pages: {
|
|
isGlobal: false, // single doc per tenant?
|
|
useBaseFilter: true, // filter list + relationships
|
|
useTenantAccess: true, // enforce tenant access control
|
|
customTenantField: false, // manually place tenantField export
|
|
accessResultOverride: ..., // override access control result
|
|
}
|
|
}
|
|
```
|
|
|
|
## Frontend Querying
|
|
|
|
Filter any enabled collection by tenant field:
|
|
|
|
```tsx
|
|
const pages = await payload.find({
|
|
collection: 'pages',
|
|
where: {
|
|
'tenant.slug': { equals: 'gold' },
|
|
},
|
|
overrideAccess: false,
|
|
})
|
|
```
|
|
|
|
### Next.js Domain-Based Rewrites
|
|
|
|
Route per domain to the matching tenant:
|
|
|
|
```ts
|
|
async rewrites() {
|
|
return [{
|
|
source: '/((?!admin|api)):path*',
|
|
destination: '/:tenantDomain/:path*',
|
|
has: [{ type: 'host', value: '(?<tenantDomain>.*)' }],
|
|
}]
|
|
}
|
|
```
|
|
|
|
## `useTenantSelection` Hook
|
|
|
|
Access current tenant context in custom Admin components:
|
|
|
|
```tsx
|
|
import { useTenantSelection } from '@payloadcms/plugin-multi-tenant/client'
|
|
|
|
const { selectedTenantID, setTenant, options } = useTenantSelection()
|
|
|
|
setTenant({ id: 'abc123', refresh: true })
|
|
```
|
|
|
|
## Super-Admin Bypass
|
|
|
|
```ts
|
|
multiTenantPlugin<Config>({
|
|
collections: { pages: {} },
|
|
userHasAccessToAllTenants: (user) => user.roles?.includes('super-admin'),
|
|
})
|
|
```
|
|
|
|
## Key Takeaways
|
|
|
|
- **You own the tenants collection** — plugin just references it; define your own fields (slug, domain, plan, etc.)
|
|
- **`isGlobal: true`** enforces exactly one document per tenant (navigation, settings, etc.)
|
|
- **Cascade delete is on by default** — guard the tenants collection or disable with `cleanupAfterTenantDelete: false`
|
|
- **`useBaseFilter`** filters both the list view AND relationship pickers — setting to `false` requires manual filtering
|
|
- **`userHasAccessToAllTenants`** is the correct hook for super-admin users, not disabling `useTenantAccess`
|
|
- **`useTenantSelection`** hook lets custom Admin components read/set the active tenant
|
|
- **Domain routing**: combine with Next.js rewrites to map domains → tenant slugs automatically
|
|
|
|
## Related
|
|
|
|
- [[wiki/payloadcms/plugins|Official Plugins]] — all 10 official plugins overview
|
|
- [[wiki/payloadcms/access-control|Access Control]] — how access control integrates with tenant isolation
|
|
- [[wiki/payloadcms/collection-config|Collection Config]] — collection-level options used by the plugin
|
|
- [[wiki/payloadcms/authentication-overview|Authentication Overview]] — user collection that gets the tenants array field
|
|
|
|
## Sources
|
|
|
|
- `raw/plugins__multi-tenant.md`
|
|
- https://payloadcms.com/docs/plugins/multi-tenant
|