Summary
On Payload 3.84.1 with gatekeeper 1.1.0, the role column in the Users
list view renders empty (<No Role>) for every user, even a super admin whose
role clearly exists and has a label. The role assignment itself works correctly —
only the list-view display is broken.
Two things combine here, and the second one removes the obvious workaround.
Environment
- payload-gatekeeper: 1.1.0 (latest)
- payload: 3.84.1
- DB: MongoDB
- (Note: gatekeeper's own docs screenshot was taken against Payload 3.52.0, where
this renders fine. I couldn't pin the exact Payload change between 3.52 and
3.84, so I'm reporting observed behavior on 3.84.1.)
Bug 1 — list cell can't render the populated role object
createAfterReadHook (src/hooks/afterReadHook.ts) replaces the role ID with
a fully-populated role object on every read:
if (doc?.role && (typeof doc.role === 'string' || typeof doc.role === 'number')) {
doc.role = await req.payload.findByID({ collection: getRolesSlug(), id: ..., depth: 0 })
}
Payload's default RelationshipCell only builds its display value from a plain ID
(string/number) or a { relationTo, value } pair. A fully-populated object
matches neither branch, so its formattedValues array stays empty, it never calls
getRelationships, and it falls back to general:noLabel → <No Role>.
Verified directly: GET /api/users?depth=0 returns role as a full object (not an
ID), confirming the hook fires for list/collection reads on 3.84.
Importantly, the populated object can't just be dropped — req.user.role.permissions
is exactly what createCollectionAccess and createUIVisibilityCheck read on every
request, so removing/guarding the population would break access + sidebar visibility.
The only safe layer to fix is display.
Bug 2 — roleFieldConfig is documented but never applied
The README documents roleFieldConfig for customizing the role field, and
src/index.ts passes it through:
const enhanceOptions = { ...options, roleFieldPlacement: ..., roleFieldConfig: collectionConfig.roleFieldConfig }
enhanceAdminCollection(processedCollection, enhanceOptions)
…but enhanceCollectionWithRole (src/utils/enhanceAdminCollection.ts) only reads
options.skipPermissionChecks, options.skipIfRoleExists, and
options.roleFieldPlacement. options.roleFieldConfig is never used, so any
customization passed through it (e.g. admin.components.Cell to fix Bug 1) is
silently ignored.
Repro
- Payload 3.84.x + gatekeeper 1.1.0, MongoDB.
- Enhance the users collection, assign any role to a user.
- Open the Users list view → the Role column shows
<No Role> for all rows.
- Try to fix it via
roleFieldConfig: { admin: { components: { Cell: ... } } }
→ no effect (Bug 2).
Suggested fixes
- (A) Ship a default
admin.components.Cell on the generated role field that
reads the label from the already-populated object. Fixes Bug 1 out of the box
without touching the load-bearing population.
- (B) Actually apply
options.roleFieldConfig in enhanceCollectionWithRole
so users can override the field (incl. the Cell). Fixes Bug 2.
Happy to open a PR for (A), (B), or both — let me know which shape you'd prefer.
Summary
On Payload 3.84.1 with gatekeeper 1.1.0, the
rolecolumn in the Userslist view renders empty (
<No Role>) for every user, even a super admin whoserole clearly exists and has a label. The role assignment itself works correctly —
only the list-view display is broken.
Two things combine here, and the second one removes the obvious workaround.
Environment
this renders fine. I couldn't pin the exact Payload change between 3.52 and
3.84, so I'm reporting observed behavior on 3.84.1.)
Bug 1 — list cell can't render the populated role object
createAfterReadHook(src/hooks/afterReadHook.ts) replaces the role ID witha fully-populated role object on every read:
Payload's default
RelationshipCellonly builds its display value from a plain ID(
string/number) or a{ relationTo, value }pair. A fully-populated objectmatches neither branch, so its
formattedValuesarray stays empty, it never callsgetRelationships, and it falls back togeneral:noLabel→<No Role>.Verified directly:
GET /api/users?depth=0returnsroleas a full object (not anID), confirming the hook fires for list/collection reads on 3.84.
Importantly, the populated object can't just be dropped —
req.user.role.permissionsis exactly what
createCollectionAccessandcreateUIVisibilityCheckread on everyrequest, so removing/guarding the population would break access + sidebar visibility.
The only safe layer to fix is display.
Bug 2 —
roleFieldConfigis documented but never appliedThe README documents
roleFieldConfigfor customizing the role field, andsrc/index.tspasses it through:…but
enhanceCollectionWithRole(src/utils/enhanceAdminCollection.ts) only readsoptions.skipPermissionChecks,options.skipIfRoleExists, andoptions.roleFieldPlacement.options.roleFieldConfigis never used, so anycustomization passed through it (e.g.
admin.components.Cellto fix Bug 1) issilently ignored.
Repro
<No Role>for all rows.roleFieldConfig: { admin: { components: { Cell: ... } } }→ no effect (Bug 2).
Suggested fixes
admin.components.Cellon the generated role field thatreads the label from the already-populated object. Fixes Bug 1 out of the box
without touching the load-bearing population.
options.roleFieldConfiginenhanceCollectionWithRoleso users can override the field (incl. the Cell). Fixes Bug 2.
Happy to open a PR for (A), (B), or both — let me know which shape you'd prefer.