diff --git a/raw/plugins__mcp.md b/raw/_processed/plugins__mcp.md similarity index 100% rename from raw/plugins__mcp.md rename to raw/_processed/plugins__mcp.md diff --git a/wiki/_master-index.md b/wiki/_master-index.md index ff886c4..e1d822c 100644 --- a/wiki/_master-index.md +++ b/wiki/_master-index.md @@ -35,7 +35,7 @@ This 3-hop pattern works for hundreds of articles without vector search. | [[wiki/reports/_index\|reports/]] | Weekly and monthly summaries — generate: `uv run python scripts/report-generator.py --weekly` | 1 | | [[wiki/infrastructure/_index\|infrastructure/]] | Server inventory: all 10 SSH hosts — optical, optical-dev, optical-prod, baic, librechat, modocmms, box-cli, aimpress, pve | 12 | | [[wiki/testing/_index\|testing/]] | Web app testing: functional, performance, security, UI types; TDD/BDD/Agile methodologies; Selenium/Cypress/Playwright/JMeter/OWASP ZAP tools | 1 | -| [[wiki/payloadcms/_index\|payloadcms/]] | Full Payload CMS reference — getting started, config, database (Postgres/MongoDB/SQLite), all 22 field types, access control, hooks, authentication (cookies, JWT, API keys, custom strategies, token data), admin UI, custom components, Lexical rich text, live preview, versions/drafts, Local/REST/GraphQL APIs, queries, plugins, jobs queue, upload, ecommerce, production deploy, TypeScript, migration guides, i18n, localization, hierarchy | 115 | +| [[wiki/payloadcms/_index\|payloadcms/]] | Full Payload CMS reference — getting started, config, database (Postgres/MongoDB/SQLite), all 22 field types, access control, hooks, authentication (cookies, JWT, API keys, custom strategies, token data), admin UI, custom components, Lexical rich text, live preview, versions/drafts, Local/REST/GraphQL APIs, queries, plugins, jobs queue, upload, ecommerce, production deploy, TypeScript, migration guides, i18n, localization, hierarchy | 116 | | [[wiki/shared-patterns/_index\|shared-patterns/]] | Oliver Agency standard library patterns: httpx, structlog, pydantic-settings, alembic — reuse before writing from scratch | 4 | | [[wiki/mistakes/_index\|mistakes/]] | Anti-patterns extracted from sessions — per-stack running lists (fastapi, react, docker, postgres, general) — injected at session start | 5 | diff --git a/wiki/payloadcms/_index.md b/wiki/payloadcms/_index.md index 8229672..913034d 100644 --- a/wiki/payloadcms/_index.md +++ b/wiki/payloadcms/_index.md @@ -117,3 +117,4 @@ | [[wiki/payloadcms/plugin-form-builder\|Form Builder Plugin]] | Dynamic admin-built forms, all field types, upload/payment/email, access gotchas, frontend multipart submission, presigned URL pattern | raw/plugins__form-builder.md | 2026-05-15 | | [[wiki/payloadcms/plugin-import-export\|Import/Export Plugin]] | CSV/JSON import-export via admin UI or API: jobs queue setup, import modes (create/update/upsert), batch hooks, column mapping, access control warning | raw/plugins__import-export.md | 2026-05-15 | | [[wiki/payloadcms/plugin-mcp\|MCP Plugin]] | Expose Payload as an MCP server — API key access control (two-step), custom tools/prompts/resources, token efficiency (select + overrideResponse), Claude Code / Cursor / VS Code client config | raw/plugins__mcp.md | 2026-05-15 | +| [[wiki/payloadcms/plugin-multi-tenant\|Multi-Tenant Plugin]] | Scaffold multi-tenancy: tenant field on collections, Admin selector, filtered list views + relationship pickers, isGlobal (1 doc/tenant), cascade delete, super-admin bypass, useTenantSelection hook | raw/plugins__multi-tenant.md | 2026-05-15 | diff --git a/wiki/payloadcms/plugin-multi-tenant.md b/wiki/payloadcms/plugin-multi-tenant.md new file mode 100644 index 0000000..d983201 --- /dev/null +++ b/wiki/payloadcms/plugin-multi-tenant.md @@ -0,0 +1,158 @@ +--- +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({ + 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: '(?.*)' }], + }] +} +``` + +## `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({ + 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