
Research
Malicious npm Packages Impersonate Flashbots SDKs, Targeting Ethereum Wallet Credentials
Four npm packages disguised as cryptographic tools steal developer credentials and send them to attacker-controlled Telegram infrastructure.
payload-gatekeeper
Advanced tools
The ultimate access control gatekeeper for Payload CMS v3 - Advanced RBAC with wildcard support, auto role assignment, and flexible configuration
A comprehensive, production-ready permissions system with role-based access control, automatic permission generation, and flexible configuration options. Your collections' trusted guardian.
*
, collection.*
)Payload Gatekeeper provides a clean and intuitive interface for managing roles and permissions:
The Roles collection showing system default roles - fully manageable through the UI
Assigning roles when creating new users - with searchable dropdown
Users overview showing their assigned roles
npm install payload-gatekeeper
# or
yarn add payload-gatekeeper
# or
pnpm add payload-gatekeeper
// payload.config.ts
import { buildConfig } from 'payload'
import { gatekeeperPlugin } from 'payload-gatekeeper'
export default buildConfig({
// ... your config
plugins: [
gatekeeperPlugin({
// Minimal config - just enhance your admin collection
collections: {
'users': {
enhance: true,
autoAssignFirstUser: true,
}
},
// Exclude collections from permission system entirely
excludeCollections: ['special-config'] // These use their own access control
})
]
})
When autoAssignFirstUser
is enabled, the first user automatically receives the super_admin role:
The first user gets super admin privileges automatically
import { gatekeeperPlugin } from 'payload-gatekeeper'
export default buildConfig({
plugins: [
gatekeeperPlugin({
// Configure specific collections
collections: {
'admin-users': {
enhance: true,
roleFieldPlacement: {
tab: 'Security', // Place in specific tab
position: 'first', // Position: 'first', 'last', 'sidebar', or number
},
autoAssignFirstUser: true, // First user gets super_admin role
defaultRole: undefined, // No default role for admins
},
'customers': {
enhance: true,
roleFieldPlacement: {
position: 'sidebar', // Show in sidebar
},
autoAssignFirstUser: false, // Don't make first customer super_admin
defaultRole: 'customer', // New signups get 'customer' role
},
},
// Define system roles (in addition to super_admin)
systemRoles: [
{
name: 'admin',
label: 'Administrator',
permissions: [
// UI visibility permissions
'users.manage', // Shows Users in admin UI
'products.manage', // Shows Products in admin UI
'media.manage', // Shows Media in admin UI
// CRUD permissions
'users.*', // All user operations
'products.*', // All product operations
'media.*', // All media operations
// Exclude roles.* to prevent role management
],
protected: false, // Can be modified
active: true,
description: 'Full admin access without role management',
visibleFor: ['admin-users'], // Only visible for admin users
},
{
name: 'editor',
label: 'Content Editor',
permissions: [
'products.manage', // Can see products in UI
'products.read',
'products.update',
'media.*',
],
active: true,
description: 'Can edit content and media',
},
{
name: 'customer',
label: 'Customer',
permissions: [
'orders.read', // Can view own orders (row-level handled separately)
'customers.read', // Can view own profile
'customers.update', // Can update own profile
],
active: true,
description: 'Default customer role',
visibleFor: ['customers'], // Only visible for customer users
},
],
// Optional: Exclude collections from permission system
excludeCollections: ['public-pages'],
// Environment-based options
skipPermissionChecks: false, // Set to true during seeding/migration
syncRolesOnInit: process.env.SYNC_ROLES === 'true',
// UI customization
rolesGroup: 'Admin', // Group name for Roles collection in admin panel (default: 'System')
rolesSlug: 'roles', // Custom slug for Roles collection (default: 'roles')
})
]
})
Non-authenticated users automatically have configurable public access:
// Default behavior - public can read all non-auth collections
gatekeeperPlugin({})
// Custom public permissions
gatekeeperPlugin({
publicRolePermissions: [
'*.read', // Read all collections
'comments.create', // Can create comments
'reactions.create' // Can add reactions
]
})
// Completely private system
gatekeeperPlugin({
disablePublicRole: true // No public access at all
})
Important: Auth-enabled collections (users, admins) are ALWAYS protected from public access, regardless of public permissions.
The plugin automatically creates a Roles collection where you can manage all roles through the UI:
Creating a new editor role with specific permissions
Roles collection showing both system and custom roles
The plugin supports various permission patterns:
*
- Super admin with access to everythingcollection.*
- All operations on a specific collectioncollection.read
- Specific operation on a collection*.read
- Read access to all collectionscollection.manage
- UI visibility permission (controls whether collection appears in admin panel)Protected roles cannot be deleted and have restricted field updates. Only users with *
permission can modify protected roles.
const superAdminRole = {
name: 'super_admin',
permissions: ['*'],
protected: true, // Cannot be deleted, limited updates
}
Protected super admin role showing the lock indicator and full permissions
The plugin separates UI visibility from CRUD operations:
.manage
permission - Controls whether a collection appears in the admin UI.read
, .create
, .update
, .delete
) - Control actual data operations// User can see the collection in UI but only read data
permissions: ['products.manage', 'products.read']
// User can perform operations but collection is hidden in UI
permissions: ['products.create', 'products.update']
Define application-specific permissions that are automatically organized by namespace:
import { gatekeeper } from 'payload-gatekeeper'
export default buildConfig({
plugins: [
gatekeeper({
customPermissions: [
// Event Management permissions (namespace: event-management)
{
label: 'Export Events',
value: 'event-management.export',
description: 'Export event data to CSV/Excel'
},
{
label: 'Import Events',
value: 'event-management.import',
description: 'Import events from external sources'
},
{
label: 'Manage Templates',
value: 'event-management.templates',
description: 'Create and manage event templates'
},
// Marketing permissions (namespace: marketing)
{
label: 'Send Newsletters',
value: 'marketing.newsletters',
description: 'Send marketing newsletters to users'
},
{
label: 'Manage Campaigns',
value: 'marketing.campaigns',
description: 'Create and manage marketing campaigns'
},
// Analytics permissions (namespace: analytics)
{
label: 'View Analytics',
value: 'analytics.view',
description: 'View platform analytics and metrics'
},
{
label: 'Export Reports',
value: 'analytics.export',
description: 'Export analytics reports'
}
]
})
]
})
Custom permissions appear automatically in the Roles management UI, grouped by their namespace. Use them in your code:
// In API endpoints or hooks
import { checkPermission } from 'payload-gatekeeper'
export const exportEventHandler = async (req, res) => {
const canExport = await checkPermission(
req.payload,
req.user.role,
'event-management.export',
req.user.id
)
if (!canExport) {
return res.status(403).json({ error: 'Not authorized to export events' })
}
// Export logic here...
}
The namespace (part before the dot) in the permission value is automatically extracted and formatted as the category:
event-management.export
→ Category: "Event Management"backend-users.impersonate
→ Category: "Backend Users"user-profiles.manage
→ Category: "User Profiles"This keeps your permissions organized in the UI without explicit grouping configuration.
event-management
)export
)label
: Display name in the UIvalue
: Unique identifier with namespace.operation formatdescription
: Optional helper text shown in the UICustom permissions integrate seamlessly with the existing permission system, supporting the same wildcards and inheritance patterns.
Users can only assign roles whose permissions are a subset of their own:
users.*
and media.*
can assign roles with users.read
or media.update
users.*
cannot assign a role with products.*
(they don't have product permissions)*
permission can assign protected rolesThe plugin is designed to work seamlessly with multiple auth-enabled collections. Each collection can have its own:
Many applications need different types of users:
Each can have their own collection with tailored fields, while sharing the same role-based permission system.
gatekeeperPlugin({
collections: {
'users': {
enhance: true,
autoAssignFirstUser: true,
}
}
})
gatekeeperPlugin({
collections: {
'admins': {
enhance: true,
roleFieldPlacement: {
tab: 'Security', // Create/use 'Security' tab
position: 'first' // Place at the beginning of the tab
},
autoAssignFirstUser: true,
},
'customers': {
enhance: true,
roleFieldPlacement: {
position: 'sidebar' // Show in the sidebar for easy access
},
defaultRole: 'customer',
},
'vendors': {
enhance: true,
roleFieldPlacement: {
tab: 'Account', // Place in existing 'Account' tab
position: 2 // Place as third field (0-indexed)
},
defaultRole: 'vendor',
},
'partners': {
enhance: true,
roleFieldPlacement: {
position: 'last' // Place at the end of fields
},
defaultRole: 'partner',
}
},
systemRoles: [
// ... role definitions
]
})
You can further customize the role field for each collection:
gatekeeperPlugin({
collections: {
'users': {
enhance: true,
roleFieldConfig: {
label: 'Access Level', // Custom label
admin: {
description: 'Controls what this user can access in the system',
position: 'sidebar',
condition: (data) => data.active === true, // Only show for active users
}
}
}
}
})
When autoAssignFirstUser: true
is configured, you don't need to search for roles - the first user automatically gets super_admin:
// seed-admin.ts
import { getPayload } from 'payload'
import config from './payload.config'
async function seedFirstAdmin() {
const payload = await getPayload({ config })
try {
// Check if any admin users exist
const existingUsers = await payload.count({
collection: 'users',
})
if (existingUsers.totalDocs > 0) {
console.log('Admin users already exist, skipping seed')
return
}
// Create the first admin user - automatically gets super_admin role!
await payload.create({
collection: 'users',
data: {
email: 'admin@example.com',
password: 'SecurePassword123!',
// No need to set role - autoAssignFirstUser handles it
},
})
console.log('✅ First admin user created with super_admin role')
} catch (error) {
console.error('Error seeding admin:', error)
}
process.exit(0)
}
seedFirstAdmin()
Only search for roles when you need to create additional users with specific roles:
// seed-users.ts
async function seedUsers() {
const payload = await getPayload({ config })
try {
// Find specific roles for additional users
const editorRole = await payload.find({
collection: 'roles',
where: { name: { equals: 'editor' } },
limit: 1,
})
const viewerRole = await payload.find({
collection: 'roles',
where: { name: { equals: 'viewer' } },
limit: 1,
})
// Create editor user
if (editorRole.docs.length > 0) {
await payload.create({
collection: 'users',
data: {
email: 'editor@example.com',
password: 'EditorPass123!',
role: editorRole.docs[0].id,
},
})
}
// Create viewer user
if (viewerRole.docs.length > 0) {
await payload.create({
collection: 'users',
data: {
email: 'viewer@example.com',
password: 'ViewerPass123!',
role: viewerRole.docs[0].id,
},
})
}
console.log('✅ Additional users created')
} catch (error) {
console.error('Error seeding users:', error)
}
}
seedUsers()
import { checkPermission, hasPermission } from 'payload-gatekeeper'
// In your custom endpoint or hook
async function myCustomEndpoint(req: PayloadRequest) {
// Check if user has specific permission
const canEdit = await checkPermission(
req.payload,
req.user.role,
'products.update',
req.user.id
)
if (!canEdit) {
throw new Error('Unauthorized')
}
// Direct permission check (if you have the permissions array)
const permissions = req.user.role?.permissions || []
const canDelete = hasPermission(permissions, 'products.delete')
}
Option | Type | Description | Default |
---|---|---|---|
collections | object | Collection-specific configuration | {} |
systemRoles | array | Roles to create/sync on init | [] |
excludeCollections | string[] | Collections to exclude from permission system | [] |
disablePublicRole | boolean | Disable public access for non-authenticated users | false |
publicRolePermissions | string[] | Custom permissions for public users | ['*.read'] |
skipPermissionChecks | boolean | (() => boolean) | Skip permission checks (for seeding/migration) | false |
syncRolesOnInit | boolean | Force role sync on every init | false |
rolesGroup | string | UI group name for Roles collection | 'System' |
rolesSlug | string | Custom slug for Roles collection | 'roles' |
Option | Type | Description | Default |
---|---|---|---|
enhance | boolean | Add role field to collection | false |
roleFieldPlacement | object | Where to place the role field | undefined |
autoAssignFirstUser | boolean | Assign super_admin to first user | false |
defaultRole | string | Default role for new users | undefined |
checkPermission(payload, userRole, permission, userId?)
Checks if a user has a specific permission. Handles role loading and wildcard matching.
hasPermission(permissions, requiredPermission)
Direct permission check against an array of permissions. Supports wildcards.
canAssignRole(userPermissions, targetRole)
Checks if a user can assign a specific role based on permission subset logic.
Control which roles appear in the role selection dropdown for each collection:
gatekeeperPlugin({
collections: {
'backend-users': {
enhance: true,
autoAssignFirstUser: true, // Makes this an "admin collection"
},
'customers': {
enhance: true,
defaultRole: 'customer',
}
},
systemRoles: [
{
name: 'admin',
label: 'Administrator',
permissions: ['users.*'],
visibleFor: ['backend-users'], // Only shown for backend-users
},
{
name: 'customer',
label: 'Customer',
permissions: ['orders.read'],
visibleFor: ['customers'], // Only shown for customers
},
{
name: 'viewer',
label: 'Read-Only Access',
permissions: ['*.read'],
// No visibleFor = shown for all collections
}
]
})
Intelligent Super Admin Visibility: The Super Admin role is automatically set to be visible only for collections with autoAssignFirstUser: true
(admin collections). This prevents customers or other non-admin users from seeing or being assigned the Super Admin role.
If you already have a roles
collection in your project, you can configure the plugin to use a different slug:
gatekeeperPlugin({
rolesSlug: 'admin-roles', // Use 'admin-roles' instead of 'roles'
rolesGroup: 'Admin', // Optional: also change the UI group
// ... other config
})
Note: When using a custom slug, make sure to update any references in your seed scripts or custom code.
You can add custom permissions beyond CRUD operations:
systemRoles: [
{
name: 'moderator',
permissions: [
'comments.approve', // Custom permission
'comments.flag', // Custom permission
'users.ban', // Custom permission
]
}
]
The plugin wraps existing access control, allowing collections to maintain their own logic:
// Your collection's existing access control still works
const Products: CollectionConfig = {
access: {
read: ({ req }) => {
// Your custom logic here
return req.user?.company === 'allowed-company'
}
}
}
// Plugin adds permission check on top:
// 1. First checks permission (products.read)
// 2. Then checks your custom logic
// Both must pass for access to be granted
The plugin doesn't read environment variables directly. You need to configure them in your plugin options:
// payload.config.ts
gatekeeperPlugin({
// Use environment variables in your config
syncRolesOnInit: process.env.SYNC_ROLES === 'true',
skipPermissionChecks: process.env.SKIP_PERMISSIONS === 'true',
// Or use a function for dynamic control
skipPermissionChecks: () => process.env.NODE_ENV === 'seed',
})
Then run your application with environment variables:
# Force role synchronization
SYNC_ROLES=true npm run dev
# Skip permissions during seeding
NODE_ENV=seed npm run seed
# Development mode (auto-syncs roles when NODE_ENV=development)
NODE_ENV=development npm run dev
autoAssignFirstUser: true
automatically receives the super_admin role*
grants complete system accessInitial Setup:
Role Management:
Backup Considerations:
roles
collection before updatesThe plugin is fully typed. Types are automatically generated for your roles and permissions:
import type { Role, Permission } from 'payload-gatekeeper'
// Your role documents will have proper typing
const role: Role = {
name: 'admin',
permissions: ['users.*', 'products.read'],
// ...
}
# Run all tests
npm test
# Run tests with coverage report
npm run test:coverage
# Run tests in watch mode
npm run test:watch
# Run specific test file
npm test checkUIVisibility
The project maintains high test coverage standards:
Type | Threshold | Current |
---|---|---|
Lines | 80% | 90.73% ✅ |
Branches | 80% | 85.17% ✅ |
Functions | 80% | 82.5% ✅ |
Statements | 80% | 90.73% ✅ |
# Install dependencies
npm install
# Build the plugin
npm run build
# Run linting
npm run lint
# Fix linting issues
npm run lint:fix
# Type checking
npm run typecheck
# Run all quality checks
npm run ci
src/
├── access/ # Access control utilities
├── collections/ # Roles collection definition
├── components/ # React components (role selector)
├── hooks/ # Payload hooks (beforeChange, afterChange, etc.)
├── utils/ # Utility functions
├── types.ts # TypeScript type definitions
├── constants.ts # Constants and defaults
├── defaultRoles.ts # System default roles
└── index.ts # Main plugin export
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '20'
- run: npm ci
- run: npm run lint
- run: npm run typecheck
- run: npm run test:coverage
- uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.info
MIT License - see LICENSE file for details
Contributions are welcome! Please read our contributing guidelines before submitting PRs.
git checkout -b feature/amazing-feature
)npm test
)npm run test:coverage
)npm run lint
)git commit -m 'Add amazing feature'
)git push origin feature/amazing-feature
)For issues, questions, or suggestions, please open an issue on GitHub.
Built with ❤️ for the Payload CMS community.
FAQs
The ultimate access control gatekeeper for Payload CMS v3 - Advanced RBAC with wildcard support, auto role assignment, and flexible configuration
The npm package payload-gatekeeper receives a total of 5 weekly downloads. As such, payload-gatekeeper popularity was classified as not popular.
We found that payload-gatekeeper demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Four npm packages disguised as cryptographic tools steal developer credentials and send them to attacker-controlled Telegram infrastructure.
Security News
Ruby maintainers from Bundler and rbenv teams are building rv to bring Python uv's speed and unified tooling approach to Ruby development.
Security News
Following last week’s supply chain attack, Nx published findings on the GitHub Actions exploit and moved npm publishing to Trusted Publishers.