obsidian/wiki/payloadcms/plugin-multi-tenant.md
2026-05-15 16:20:04 +01:00

5.1 KiB

title aliases tags sources created updated
Multi-Tenant Plugin
multi-tenancy
plugin-multi-tenant
tenant-isolation
payloadcms
plugins
multi-tenant
access-control
raw/plugins__multi-tenant.md
2026-05-15 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 collectionsisGlobal: 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

pnpm add @payloadcms/plugin-multi-tenant

Basic Config

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

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:

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:

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:

import { useTenantSelection } from '@payloadcms/plugin-multi-tenant/client'

const { selectedTenantID, setTenant, options } = useTenantSelection()

setTenant({ id: 'abc123', refresh: true })

Super-Admin Bypass

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

Sources