FEATURED · 精选文章

Payload Multi-Tenant 插件指南:在管理后台内实现按租户的数据隔离与管理

发布时间 / 2026/9/10 14:32:59
来源 / 创域科博编辑部
栏目 / 资讯中心
Payload Multi-Tenant 插件指南:在管理后台内实现按租户的数据隔离与管理 Payload Multi-Tenant 插件指南在管理后台内实现按租户的数据隔离与管理【免费下载链接】payloadPayload is the open-source, fullstack Next.js framework, giving you instant backend superpowers. Get a full TypeScript backend and admin panel instantly. Use Payload as a headless CMS or for building powerful applications.项目地址: https://gitcode.com/GitHub_Trending/pa/payload多租户Multi-Tenancy是 SaaS 类应用最常见的需求之一。本指南以当前仓库中payloadcms/plugin-multi-tenant源码位于 packages/plugin-multi-tenant为讲解对象说明如何在不写大量重复代码的前提下通过插件向 Payload 的集合注入租户字段、租户选择器、列表过滤与访问控制实现一套 Payload 实例、多租户数据天然隔离。读完本文你将掌握插件全部配置项与默认值、如何把集合当 Global 使用、如何自定义访问控制以支持跨租户共享文档、如何手动接管 users 集合上的租户数组字段以及插件底层集合改造、base access 包装、Cookie 上下文是如何工作的。一、插件要解决什么问题该插件解决的核心问题是在 Payload 中快速搭建多租户能力。其底层实现入口文件 src/index.ts是一个标准的definePlugin在 Payload 构建配置时做集合改造主要完成向所有开启多租户的集合注入一个指向tenants集合的tenant关联字段默认字段名为tenant在 Admin UI 中注入一个租户选择器Tenant Selector让管理员可以切换当前上下文租户依据当前选中的租户自动过滤列表视图结果并对关系字段的候选值filterOptions做同样约束在 users 集合上注入一个tenants数组字段记录用户可访问的租户并把约束写入访问控制支持把某些集合当作 Global 使用每租户一份文档隐藏列表视图新文档自动带上当前选中租户作为默认值在删除租户时默认清理关联文档并移除用户对该租户的引用。这些能力都可以从 源码结构 中一一得到印证例如filters/filterDocumentsByTenants.ts、utilities/addCollectionAccess.ts、components/TenantSelector/、hooks/afterTenantDelete.ts等模块各司其职。二、安装与运行前提安装插件package.json 中定义包名为payloadcms/plugin-multi-tenantpnpm add payloadcms/plugin-multi-tenant安装完成后还需满足两个硬性前提你必须自己创建tenants集合并自行决定它包含哪些字段例如name、slug、domain。插件不会替你创建它。若在你的配置里找不到对应 slug 的集合插件会直接抛出Tenants collection not found with slug: ...错误见 src/index.ts。配置中必须存在一个开启 auth 的集合通常是users。插件会用它作为管理员用户集合并注入租户数组字段如果找不到会抛出An auth enabled collection was not found见 src/index.ts。该插件把payload与payloadcms/ui声明为 peer 依赖需要与你的 Payload 版本配套使用。三、插件配置项全解multiTenantPlugin的配置结构定义在 src/types.ts其中每个字段的默认值集中在 src/defaults.ts。下面按类别逐一说明。3.1 顶层开关类配置项类型默认值作用enabledbooleantrue设为false直接禁用插件src/index.ts。debugbooleanfalse开启调试模式让tenant字段在 Admin UI 中可见正常模式下它会被隐藏改由独立的 Assign Tenant 操作维护。tenantsSlugstringtenants指定 tenants 集合的 slug用于插件定位它。cleanupAfterTenantDeletebooleantrue删除租户后是否清理其关联文档、并把该租户从所有用户的tenants数组中移除。3.2 collections按集合粒度开启多租户collections是必传的核心选项键为集合 slugcollections: { pages: {}, navigation: { isGlobal: true }, media: { useTenantAccess: false }, }每个集合可用的子选项如下子选项默认值作用isGlobalfalse设为true后集合按 Global 方式工作隐藏列表视图并且每个租户只允许一条文档插件会为其 tenant 字段加unique约束并禁止复制见 src/index.ts。useBaseFiltertrue设为false表示你不希望插件自动叠加按当前选中租户过滤列表的 baseFilter改为手动实现。useBaseListFiltertrue已废弃旧版选项功能被useBaseFilter取代源码中当两者同时出现时useBaseFilter优先src/types.ts。useTenantAccesstrue设为false表示该集合访问控制完全由你手动接管不再叠加插件生成的租户约束。适用于需要部分文档跨租户共享的场景见第六节。customTenantFieldfalse设为true后插件不为该集合注入tenant字段你需要用插件导出的tenantField自行放置。tenantFieldOverrides-该集合独有的 tenant 字段覆盖配置优先于顶层tenantField见 src/index.ts。accessResultOverride-覆写该集合访问控制结果。回调接收accessResult原始访问结果、accessKeycreate/read/update/delete/readVersions/unlock以及其余 Access 参数返回新的AccessResultsrc/types.ts。3.3 tenantField注入业务集合的租户字段控制添加到所有开启多租户集合上的那个关联字段默认名tenant。可用配置子选项默认值作用nametenant字段名称。access{}字段级访问控制类型为RelationshipField[access]。从字段工厂 src/fields/tenantField/index.ts 可以看到该字段的真实形态一个指向tenantsSlug的relationship字段hasMany默认false放置于 Admin 侧边栏position: sidebar关闭了创建/编辑与列/筛选/分组显示allowCreate/allowEdit为 falsecolumn/filter/groupBy禁用并默认做了两件事默认值自动填充优先读取当前请求携带的payload-tenantCookie 对应的租户校验其有效性后作为默认值若集合开启了 autosave则回退到用户tenants数组中的第一个租户src/fields/tenantField/index.ts。filterOptions 约束该字段在 UI 中只能从当前用户已分配的租户里选择src/fields/tenantField/index.ts。3.4 tenantsArrayField注入 users 集合的租户数组字段控制添加到用户集合上的tenants数组字段记录这个用户能访问哪些租户。配置为联合类型tenantsArrayField?: { arrayFieldAccess?: ArrayField[access] // 数组字段本身的访问控制 arrayFieldName?: string // 数组字段名默认 tenants arrayTenantFieldName?: string // 数组内租户关系字段名默认 tenant includeDefaultField?: true // 默认行为自动注入到 users 集合 rowFields?: Field[] // 在每行追加的自定义字段 tenantFieldAccess?: RelationshipField[access] // 行内租户关系字段的访问控制 } // 或 includeDefaultField: false 时其余选项不可用见第五节的联合类型定义字段工厂 src/fields/tenantsArrayField/index.ts 会生成一个名为tenants的array每行包含一个指向 tenants 集合的必填 relationshiprequired: true、index: true并且数组与行内字段都设置了saveToJWT: true保证租户 ID 会进入 JWT便于服务端在各处校验。注意联合类型约束当includeDefaultField为false时arrayFieldAccess、rowFields、tenantFieldAccess均不允许再配置类型上为never因为你将手动接管整个字段。3.5 全局类选项配置项类型默认值作用userHasAccessToAllTenants(user) boolean恒返回false判定某用户是否为可访问所有租户的超级管理员。若返回true插件会跳过大部分租户约束。usersAccessResultOverrideCollectionAccessResultOverride-覆写 users 集合访问控制结果签名同集合级accessResultOverride。useUsersTenantFilterbooleantrue是否在 users 集合上叠加按当前选中租户过滤列表的 baseFilter。useTenantsCollectionAccessbooleantrue是否给 tenants 集合本身加上只能访问被分配租户的访问约束。useTenantsListFilterbooleantrue是否给 tenants 集合叠加按当前选中租户过滤列表的 baseFilter。tenantSelectorLabelstring或Record语言代码, stringTenant自定义租户选择器文案支持 i18n 多语言对象在 types.ts 中被标记为已废弃推荐改用i18n。i18n{ translations: { [locale]: {...} } }-覆盖插件界面文案可用 key 包括assign-tenant-button-label默认Assign Tenant、assign-tenant-modal-title默认Assign {{title}}、field-assignedTenant-label默认Assigned Tenant、nav-tenantSelector-label默认Filter by Tenant。需要指出的是collections的键不能包含 users 集合——插件会打印警告并跳过对该集合的 tenant 字段注入以避免双重访问控制src/index.ts。四、基础用法配置 payload.config.ts在 配置文档 docs/plugins/multi-tenant.mdx 中给出了完整示例。核心步骤是先定义好tenants集合字段完全归你所有再在plugins数组中调用multiTenantPlugin并指定哪些集合开启多租户import { buildConfig } from payload import { multiTenantPlugin } from payloadcms/plugin-multi-tenant import type { Config } from ./payload-types const config buildConfig({ collections: [ { slug: tenants, admin: { useAsTitle: name, // 租户选择器依赖该字段展示名称 }, fields: [ // tenants 集合的字段由你定义以下只是建议示例 { name: name, type: text, required: true }, { name: slug, type: text, required: true }, { name: domain, type: text, required: true }, ], }, ], plugins: [ multiTenantPluginConfig({ collections: { pages: {}, navigation: { isGlobal: true, }, }, }), ], }) export default config插件允许传入 Payload 生成的类型Config这样userHasAccessToAllTenants等回调可以获得精确的user类型。插件对集合的改造包括在pages中注入tenant字段把navigation处理成每租户一份的 Global 式集合向users注入租户数组字段在 Admin 的providers、actions、beforeNav中分别挂入TenantSelectionProvider、Global 重定向组件与租户选择器见 src/index.ts。五、插件默认的数据模型与上下文机制为理解插件的各种开关先掌握它默认在数据层与运行时做了什么users 上的关联结构。插件默认向 users 注入tenants: Array{ tenant: Relationship - tenants 集合required, saveToJWT // 你通过 rowFields 追加的字段 }业务集合上的 tenant 字段。每个开启多租户的集合拥有一个tenantrelationship指向 tenants全局/Global 式集合该字段是unique。运行时当前租户上下文。租户选择器选中的值会写入名为payload-tenant的 CookieCookie 名可在 src/hooks/afterTenantDelete.ts 的清理逻辑中看到那里会用generateCookie以相同名称清除过期 Cookie。列表过滤src/filters/filterDocumentsByTenants.ts的执行顺序是优先使用当前文档的租户/显式传入的docTenantID其次读取payload-tenantCookie再次回退到用户被分配的租户集合最后返回null交由访问控制兜底。关系字段候选值也被过滤。插件会递归扫描开启多租户集合上的 relationship 字段含 blocks 内引用并注入filterOptions避免出现可以选择其他租户的文档这种越权场景相关逻辑见 src/utilities/addFilterOptionsToFields.ts。六、把集合配置成Global每租户一份内容多租户场景下很多全站性内容导航、站点设置、页头页脚也需要按租户区分但 Global 本身无法按租户分数据。官方给出的方案是Global 需要用集合实现再在插件里把它标记为isGlobal。这样插件会隐藏它的列表视图、为 tenant 字段加unique约束同一租户只能存在一份并自动处理文档切换/重定向逻辑multiTenantPlugin({ collections: { navigation: { isGlobal: true, }, }, })对应实现中isGlobal的集合会进入globalCollectionSlugs分组被设置disableDuplicate true并在存在这类集合时为 Admin 注入GlobalViewRedirectaction 组件见 src/index.ts。七、访问控制原理与自定义7.1 默认行为AND 合并租户约束插件默认把你自己写的访问控制结果与文档 tenant 必须 ∈ 当前用户可访问租户的约束做AND合并施加在baseAccess层对 read/update/delete 等均生效见 src/utilities/addCollectionAccess.ts。也就是说用户自己声明的 access 条件与租户约束必须同时满足若用户未分配任何租户则只能看到自己的用户记录或什么都看不到src/utilities/addCollectionAccess.ts。约束查询体由 getTenantAccess 生成形如{ [fieldName]: { in: userAssignedTenantIDs } }7.2 自定义手动接入租户约束以支持共享文档AND 语义并不适合某些文档在所有租户间共享。此时官方推荐为该集合关闭useTenantAccess在自定义read访问控制中把共享条件与租户约束用OR组合起来。READMEpackages/plugin-multi-tenant/README.md给出了完整可运行示例关键思路是复用插件导出的getTenantAccess来构造租户约束而不是自己手写// File: payload.config.ts import { buildConfig } from payload import { multiTenantPlugin } from payloadcms/plugin-multi-tenant import { getTenantAccess } from payloadcms/plugin-multi-tenant/utilities import { Config as ConfigTypes } from ./payload-types export default buildConfig({ plugins: [ multiTenantPluginConfigTypes({ collections: { media: { useTenantAccess: false, // 关闭默认租户访问约束改为手动控制 }, }, }), ], collections: [ { slug: media, fields: [ { name: isShared, type: checkbox, defaultValue: false, // 建议对这个是否共享字段设置访问控制防止普通用户随意修改 }, ], access: { read: ({ req, doc }) { if (!req.user) return false const whereConstraint { or: [ { isShared: { equals: true } }, // 共享文档对所有登录用户可见 ], } const tenantAccessResult getTenantAccess({ user: req.user }) if (tenantAccessResult) { whereConstraint.or.push(tenantAccessResult) } return whereConstraint }, }, }, ], })这里getTenantAccess返回{ tenant: { in: [...] } }形式的 Where 约束把它 push 进or数组即得到共享文档或归属于我所在租户的文档这一符合业务直觉的可见性规则。若使用自定义 access 后仍希望租户约束生效可用集合级accessResultOverride/usersAccessResultOverride在插件结果之上再做一层覆写。八、手动放置 users 集合上的租户数组字段默认情况下插件会把tenants数组字段追加到 users 集合字段的末尾。若你想把它放进 Tab、侧边栏或行/折叠区或修改其部分属性可设置tenantsArrayField.includeDefaultField: false然后手动把插件导出的数组字段合并进自己的字段定义import type { CollectionConfig } from payload import { tenantsArrayField } from payloadcms/plugin-multi-tenant/fields const customTenantsArrayField tenantsArrayField({ arrayFieldAccess: {}, // 数组字段的访问控制 tenantFieldAccess: {}, // 行内租户关系字段的访问控制 rowFields: [], // 每行追加的自定义字段 }) export const UsersCollection: CollectionConfig { slug: users, fields: [ { ...customTenantsArrayField, label: Associated Tenants, }, ], }README 特别强调了这个字段的摆放限制该字段不能嵌套在具名字段group、命名 Tab、array内部但可以放进 row、未命名 Tab 或 collapsible 中packages/plugin-multi-tenant/README.md。导出的tenantsArrayField工厂位于 src/fields/tenantsArrayField/index.ts同目录下还导出了用于业务集合的tenantField模块导出清单见 src/exports/fields.ts。同样地若你希望某个业务集合自行摆放 tenant 字段可设置customTenantField: true后用导出的tenantField放置。九、租户删除时的清理行为与安全提示插件会在 tenants 集合上注册afterDelete钩子src/hooks/afterTenantDelete.ts当租户被删除时默认执行两件事删除关联文档对每个开启多租户的集合执行批量deletewhere: { tenant: { in: [deletedId] } }清理用户引用查找tenants.tenant包含该租户的用户从其数组中移除对应行若被删租户恰好是当前 Cookie 中选中的租户还会下发一条过期的payload-tenantSet-Cookie把上下文清除。正因删除副作用较大配置文档 特别提醒必须为 tenants 集合配置足够严格的访问控制防止未授权用户删除租户。若你希望完全自己掌控删除流程可将顶层cleanupAfterTenantDelete设为false关闭该行为src/index.ts。十、前端如何按租户消费数据插件只负责管理与隔离数据前端查询仍需你自己完成。由于tenant就是普通关系字段可以直接在查询条件里按 tenants 集合上的业务字段过滤例如按slug或domain匹配当前请求的租户const pagesBySlug await payload.find({ collection: pages, depth: 1, draft: false, limit: 1000, overrideAccess: false, where: { tenant.slug: { equals: gold }, // 具体约束取决于你在 tenants 集合上定义的字段 }, })若采用/[tenantDomain]/[slug]的路由结构 Next.js rewrites可按域名改写请求路径把域名解析为租户标识后用于上面的查询async rewrites() { return [ { source: /((?!admin|api)):path*, destination: /:tenantDomain/:path*, has: [{ type: host, value: (?tenantDomain.*) }], }, ] }编写自定义 Admin 组件时使用 useTenantSelection插件从payloadcms/plugin-multi-tenant/client导出了若干客户端模块见 src/exports/client.tsTenantField、AssignTenantFieldTrigger、WatchTenantCollection与useTenantSelection。在 官方配置文档 中记录了useTenantSelection的用法与返回上下文import { useTenantSelection } from payloadcms/plugin-multi-tenant/client const tenantContext useTenantSelection() // { // options, // 可选的租户选项数组 // selectedTenantID, // 当前选中的租户 ID // setPreventRefreshOnChange, // 切换租户时是否阻止页面刷新浏览 Global 时建议 true // setTenant: ({ id, refresh }) void, // 切换租户可选刷新 // }同目录的providers/TenantSelectionProvider/提供实现它由插件在 Admin 的providers中自动注入TenantSelector、AssignTenantFieldModal等界面组件也依赖同一套上下文。十一、插件内部工作流与常见问题排查把 src/index.ts 从头读一遍可看到插件执行的关键流水线这对排查问题很有帮助处理enabled false的提前退出合并所有默认值defaults.tstenantsslug、tenant字段名、tenants数组字段名、选择器默认文案定位 auth 集合admin.user优先其次遍历找带auth的集合注入tenants数组字段为 users 集合叠加按选中租户过滤的 baseFilteruseUsersTenantFilter ! false时遍历所有集合找到 tenants 集合后叠加useTenantsCollectionAccess访问约束、useTenantsListFilter列表过滤、租户删除清理钩子、用于监听租户变更的_watchTenantUI 字段与getTenantOptions端点对其他声明过的集合注入tenant字段、为关系字段注入 filterOptions、叠加 baseFilter 与租户访问约束校验声明的集合都存在于配置中——若有集合在pluginConfig.collections里声明却未找到会打印黄色警告missing collections ... try placing the multi-tenant plugin after other pluginssrc/index.ts。这意味着插件顺序很重要如果其他插件也会新增/重命名集合请把本插件放在它们之后把收集到的所有租户访问约束统一写入config.baseAccessaddCollectionAccess.ts挂载 Admin 组件与 45 种语言的翻译文案见 src/translations 目录。模块导出速查定义在 package.json 的exports中导入路径内容payloadcms/plugin-multi-tenantmultiTenantPlugin主入口payloadcms/plugin-multi-tenant/fieldstenantField、tenantsArrayField字段工厂payloadcms/plugin-multi-tenant/utilitiesdefaults、getTenantAccess、getTenantListFilter即filterDocumentsByTenants、getTenantFromCookie、getUserTenantIDspayloadcms/plugin-multi-tenant/clientTenantField、AssignTenantFieldTrigger、WatchTenantCollection、useTenantSelectionpayloadcms/plugin-multi-tenant/rscTenantSelectionProvider、TenantSelector、GlobalViewRedirect等服务端组件payloadcms/plugin-multi-tenant/typesMultiTenantPluginConfig等类型payloadcms/plugin-multi-tenant/translations/languages/*单语言翻译文件综上payloadcms/plugin-multi-tenant的核心价值是把多租户这一横切关注点字段、上下文、过滤、访问控制、清理收敛为一份声明式配置collections声明哪些集合受管、isGlobal声明哪些集合按每租户单文档工作其余注入与约束由插件自动完成。当你需要突破默认行为时includeDefaultField: false、customTenantField: true、useTenantAccess: false配合./fields与./utilities导出的构件则提供了从插件默认平滑过渡到完全自定义的完整梯度。建议进一步阅读 插件主 README、配置文档 docs/plugins/multi-tenant.mdx 以及 测试目录 test/plugin-multi-tenant 中的用例以获得更多可运行的集成参考。【免费下载链接】payloadPayload is the open-source, fullstack Next.js framework, giving you instant backend superpowers. Get a full TypeScript backend and admin panel instantly. Use Payload as a headless CMS or for building powerful applications.项目地址: https://gitcode.com/GitHub_Trending/pa/payload创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻