🔓 How It Works
When you protect content with a role requirement (e.g., “member only”), users with higher-level roles (admin, super_admin) can automatically access it. This is the default behavior - you don’t need to list every role explicitly.
The astro-basics project includes a powerful setup-time role configuration system that allows you to define custom user roles for your application. This system provides compile-time type safety, zero runtime overhead, and automatic database migration generation.
The configurable role system allows you to:
Think of roles like job titles in an organization. Each role represents a level of access and responsibility:
Roles help you control who can do what in your application automatically.
Roles are arranged in a power ranking from 1 to 10:
Level 5: Super Admin ⚡ Full system access │Level 4: Admin 👔 Manage users & settings │Level 3: Moderator 🛡️ Edit & approve content │Level 2: Author ✍️ Create content │Level 1: Member 👤 View contentKey Rule: Higher level roles can do everything lower level roles can do, PLUS more.
The role system includes automatic privilege escalation through hierarchical role checking:
🔓 How It Works
When you protect content with a role requirement (e.g., “member only”), users with higher-level roles (admin, super_admin) can automatically access it. This is the default behavior - you don’t need to list every role explicitly.
⚙️ Configurable
Set useHierarchy={false} in RoleGuard components to disable hierarchy and require exact role
matching. Perfect for role-specific features that shouldn’t be accessible to higher roles.
Example:
// Protect content for members (default: hierarchical)<RoleGuard allowedRoles={['member']}> <Dashboard /></RoleGuard>
// Who can access?// ✅ member (level 1) - explicitly allowed// ✅ admin (level 2) - higher level, inherits member access// ✅ super_admin (level 3) - highest level, inherits all access// Higher roles automatically get access<RoleGuard allowedRoles={['member']}> <MemberContent /></RoleGuard>// member, admin, and super_admin can all access// Only the specified role can access<RoleGuard allowedRoles={['member']} useHierarchy={false}> <MemberOnlyContent /></RoleGuard>// ONLY members can access (admins cannot)When Hierarchy is Used:
Best Practices:
useHierarchy={false}) for role-specific featuresUse the default 3-tier system (member, admin, super_admin) if:
Add custom roles if:
Choose the pattern that matches your needs:
| Application Type | Recommended Roles | Use Case |
|---|---|---|
| Simple App | Member, Admin, Super Admin | Basic user permissions |
| Blog/Magazine | Reader, Author, Editor, Admin, Owner | Content publishing workflow |
| E-commerce | Customer, Vendor, Support, Manager, Admin, Owner | Multi-sided marketplace |
| Community Forum | Member, Contributor, Moderator, Admin, Owner | User-generated content |
| Educational | Student, Teacher, Coordinator, Admin, Superadmin | Learning management |
| SaaS Platform | Viewer, Contributor, Manager, Admin, Owner | Team collaboration |
Every configuration MUST include these three core roles:
member - Base user role (Level 1)
admin - Administrative role (Level 2+)
super_admin - System owner role (Highest level)
Before editing the configuration file, answer these questions:
Who will use your application?
Example for a blog:
✍️ Your turn: List 3-7 user types for your app:
What can each type do?
| User Type | Can View | Can Create | Can Edit | Can Delete | Can Manage Users |
|---|---|---|---|---|---|
| Reader | ✓ | ✗ | ✗ | ✗ | ✗ |
| Author | ✓ | ✓ | Own | Own | ✗ |
| Editor | ✓ | ✓ | ✓ | ✓ | ✗ |
| Admin | ✓ | ✓ | ✓ | ✓ | ✓ |
Order your roles from least to most powerful:
Convert your user types to role names:
✅ Good role names:
❌ Bad role names:
Rules for role names:
Don’t want to start from scratch? Copy one of these complete configurations that matches your needs:
Best for: Basic applications with regular users and administrators
export const roleConfig: RoleConfig = { roles: [ { name: 'member', level: 1, label: 'Member' }, { name: 'admin', level: 2, label: 'Administrator' }, { name: 'super_admin', level: 3, label: 'Super Administrator' }, ], coreRoles: ['member', 'admin', 'super_admin'],}Who has what access:
Best for: Content publishing platforms with editorial workflow
export const roleConfig: RoleConfig = { roles: [ { name: 'member', level: 1, label: 'Reader' }, { name: 'author', level: 2, label: 'Author' }, { name: 'editor', level: 3, label: 'Editor' }, { name: 'admin', level: 4, label: 'Administrator' }, { name: 'super_admin', level: 5, label: 'Owner' }, ], coreRoles: ['member', 'admin', 'super_admin'],}Who has what access:
Best for: Multi-sided marketplaces with buyers and sellers
export const roleConfig: RoleConfig = { roles: [ { name: 'member', level: 1, label: 'Customer' }, { name: 'vendor', level: 2, label: 'Vendor' }, { name: 'support', level: 3, label: 'Support Agent' }, { name: 'manager', level: 4, label: 'Manager' }, { name: 'admin', level: 5, label: 'Administrator' }, { name: 'super_admin', level: 6, label: 'Owner' }, ], coreRoles: ['member', 'admin', 'super_admin'],}Who has what access:
Best for: Discussion platforms and community sites
export const roleConfig: RoleConfig = { roles: [ { name: 'member', level: 1, label: 'Member' }, { name: 'contributor', level: 2, label: 'Contributor' }, { name: 'moderator', level: 3, label: 'Moderator' }, { name: 'curator', level: 4, label: 'Curator' }, { name: 'admin', level: 5, label: 'Administrator' }, { name: 'super_admin', level: 6, label: 'Owner' }, ], coreRoles: ['member', 'admin', 'super_admin'],}Who has what access:
Best for: Learning management systems and online courses
export const roleConfig: RoleConfig = { roles: [ { name: 'member', level: 1, label: 'Student' }, { name: 'teacher', level: 2, label: 'Teacher' }, { name: 'coordinator', level: 3, label: 'Coordinator' }, { name: 'admin', level: 4, label: 'Administrator' }, { name: 'super_admin', level: 5, label: 'Superadmin' }, ], coreRoles: ['member', 'admin', 'super_admin'],}Who has what access:
Best for: Team collaboration tools and business software
export const roleConfig: RoleConfig = { roles: [ { name: 'member', level: 1, label: 'Viewer' }, { name: 'contributor', level: 2, label: 'Contributor' }, { name: 'editor', level: 3, label: 'Editor' }, { name: 'manager', level: 4, label: 'Manager' }, { name: 'admin', level: 5, label: 'Administrator' }, { name: 'super_admin', level: 6, label: 'Owner' }, ], coreRoles: ['member', 'admin', 'super_admin'],}Who has what access:
Ready to configure? Follow these 4 steps:
Where to find it: config/roles.config.ts
What to do:
roles: section (around line 75)Annotated Example (with explanations):
export const roleConfig: RoleConfig = { roles: [ { name: 'member', // ← Internal identifier (lowercase, no spaces) level: 1, // ← Power ranking (1 = least powerful) label: 'Member', // ← Display name (what users see) }, { name: 'moderator', // ← Must be unique level: 2, // ← Must be unique label: 'Moderator', // ← Can be anything user-friendly }, { name: 'admin', // ← REQUIRED (core role) level: 3, label: 'Administrator', }, { name: 'super_admin', // ← REQUIRED (core role) level: 4, label: 'Super Administrator', }, ], coreRoles: ['member', 'admin', 'super_admin'], // ← DO NOT CHANGE THIS LINE}Understanding Each Part:
| Field | What It Is | Rules | Example |
|---|---|---|---|
| name | Internal identifier used in code | • All lowercase • No spaces (use _)• Start with letter • 2-30 characters |
content_creator |
| level | Power ranking | • Number from 1-10 • Must be unique • Higher = more power |
3 |
| label | Display name shown to users | • Any text • User-friendly • Can have spaces |
Content Creator |
DO’s and DON’Ts for Role Names:
| ✅ DO | ❌ DON’T |
|---|---|
member |
Member (has capitals) |
content_creator |
content creator (has space) |
editor |
editor! (has special character) |
level_2_user |
2nd_level_user (starts with number) |
support_agent |
support-agent (has hyphen) |
Before generating files, check your configuration is correct:
npm run validate:rolesWhat this does:
Success looks like ✓:
✓ Configuration is valid!Error looks like ✗:
Validation failed: - roles.1.name: Role name must be lowercaseIf you see errors: Go back to Step 1 and fix the issues described.
Now run the setup command:
npm run setup:rolesWhat happens:
Validation: Checks your configuration (same as Step 2)
✓ Configuration is valid!Shows Your Roles: Displays what will be generated
Current role configuration:• Member (core) - Name: member - Level: 1• Moderator - Name: moderator - Level: 2...Asks Confirmation: “Do you want to generate types and migrations?”
Y and press Enter to continueN to cancelGenerates Files: Creates 3 files automatically
✓ Types generated: src/types/generated-roles.ts✓ Migration 003 created Forward: scripts/migrations/003_user_roles.sql Rollback: scripts/migrations/rollback_003_user_roles.sqlNext steps displayed:
1. Review the generated files2. Run type-check to verify: npm run type-check3. Apply migration: npm run db:migrate -- 003_user_roles.sql4. Commit all files to GitRun the migration to update your database:
npm run db:migrateWhat this does:
user_role type with your configured rolesSuccess looks like ✓:
Migration 003_user_roles.sql applied successfullySave everything to Git:
git add config/roles.config.ts src/types/generated-roles.ts scripts/migrations/git commit -m "Configure custom roles for [your app type]"You’re done! 🎉 Your application now uses your custom roles.
Use this checklist BEFORE running npm run setup:roles:
All role names are lowercase
member, content_creator, adminMember, ContentCreator, AdminNo spaces in role names
content_creator, site_admincontent creator, site adminRole names start with a letter
moderator, level2_user2nd_moderator, 1st_tierOnly letters, numbers, and underscores
support_agent, tier_2support-agent, tier#2, admin!All three core roles present
member existsadmin existssuper_admin existsEach role has a unique name
Each role has a unique level
Levels are between 1 and 10
Levels increase with power
Levels are sequential
Role labels are user-friendly
You have 3-7 roles total
Role names match your app’s terminology
Permissions make sense
Core roles have correct levels
member should usually be level 1 (lowest)super_admin should be your highest level❌ Wrong:
{ name: 'Member', level: 1, label: 'Member' }{ name: 'Admin', level: 2, label: 'Administrator' }✅ Correct:
{ name: 'member', level: 1, label: 'Member' }{ name: 'admin', level: 2, label: 'Administrator' }Why: Role name is used internally in code and databases. It must be lowercase.
Tip: The label can have capital letters - that’s what users see!
❌ Wrong:
{ name: 'content creator', level: 2, label: 'Content Creator' }{ name: 'site admin', level: 3, label: 'Site Administrator' }✅ Correct:
{ name: 'content_creator', level: 2, label: 'Content Creator' }{ name: 'site_admin', level: 3, label: 'Site Administrator' }Why: Spaces break the code. Use underscores (_) instead.
❌ Wrong:
roles: [ { name: 'user', level: 1, label: 'User' }, // Missing 'member' { name: 'manager', level: 2, label: 'Manager' }, // Missing 'admin' { name: 'owner', level: 3, label: 'Owner' }, // Missing 'super_admin']✅ Correct:
roles: [ { name: 'member', level: 1, label: 'User' }, // Required { name: 'manager', level: 2, label: 'Manager' }, // Custom { name: 'admin', level: 3, label: 'Admin' }, // Required { name: 'super_admin', level: 4, label: 'Owner' }, // Required]Why: The three core roles (member, admin, super_admin) are required by the system.
Tip: You can change their labels, but not their names!
❌ Wrong:
{ name: 'member', level: 1, label: 'Member' }{ name: 'author', level: 2, label: 'Author' }{ name: 'editor', level: 2, label: 'Editor' } // ← Same as author!✅ Correct:
{ name: 'member', level: 1, label: 'Member' }{ name: 'author', level: 2, label: 'Author' }{ name: 'editor', level: 3, label: 'Editor' } // ← Unique levelWhy: Each role needs a unique level for the permission system to work.
❌ Wrong:
{ name: 'member', level: 0, label: 'Member' } // ← Can't use 0{ name: 'admin', level: 1, label: 'Admin' }✅ Correct:
{ name: 'member', level: 1, label: 'Member' } // ← Start at 1{ name: 'admin', level: 2, label: 'Admin' }Why: Levels must be 1-10. Zero is not allowed.
❌ Wrong:
{ name: 'content-creator', level: 2, label: 'Content Creator' }{ name: 'site-admin', level: 3, label: 'Site Admin' }✅ Correct:
{ name: 'content_creator', level: 2, label: 'Content Creator' }{ name: 'site_admin', level: 3, label: 'Site Admin' }Why: Hyphens (-) are not allowed. Only underscores (_) work.
❌ Wrong:
{ name: '2nd_tier', level: 2, label: 'Second Tier' }{ name: '1st_admin', level: 3, label: 'First Admin' }✅ Correct:
{ name: 'tier_2', level: 2, label: 'Second Tier' }{ name: 'admin_1', level: 3, label: 'First Admin' }Why: Role names must start with a letter.
❌ Confusing:
{ name: 'member', level: 5, label: 'Member' } // Why is member level 5?{ name: 'admin', level: 1, label: 'Admin' } // Why is admin level 1?✅ Clear:
{ name: 'member', level: 1, label: 'Member' } // Least powerful = level 1{ name: 'admin', level: 5, label: 'Admin' } // Most powerful = higher levelWhy: Higher levels = more permissions. Keep it logical!
❌ Wrong:
coreRoles: ['member', 'manager', 'owner'], // Changed admin and super_admin!✅ Correct:
coreRoles: ['member', 'admin', 'super_admin'], // Never change thisWhy: This line tells the system which roles are required. Don’t modify it!
⚠️ Consider:
// Do you really need all 10 of these?roles: [ { name: 'member', level: 1, label: 'Member' }, { name: 'bronze', level: 2, label: 'Bronze' }, { name: 'silver', level: 3, label: 'Silver' }, { name: 'gold', level: 4, label: 'Gold' }, { name: 'platinum', level: 5, label: 'Platinum' }, { name: 'moderator', level: 6, label: 'Moderator' }, { name: 'editor', level: 7, label: 'Editor' }, { name: 'manager', level: 8, label: 'Manager' }, { name: 'admin', level: 9, label: 'Admin' }, { name: 'super_admin', level: 10, label: 'Super Admin' },]✅ Better:
// Simplified to what you actually needroles: [ { name: 'member', level: 1, label: 'Member' }, { name: 'premium', level: 2, label: 'Premium Member' }, // Combined tiers { name: 'moderator', level: 3, label: 'Moderator' }, { name: 'admin', level: 4, label: 'Admin' }, { name: 'super_admin', level: 5, label: 'Super Admin' },]Why: Start simple! You can always add more roles later. Tip: 3-7 roles work well for most applications.
Each role must have three properties:
interface RoleDefinition { name: string // Unique identifier (lowercase, alphanumeric + underscores) level: number // Hierarchy level (1-10, higher = more privileges) label: string // Human-readable display name}Three roles are required and cannot be removed:
member - Base user roleadmin - Administrative accesssuper_admin - System administrationThese roles are required for Row Level Security (RLS) policies and system stability.
export const roleConfig: RoleConfig = { roles: [ { name: 'member', level: 1, label: 'Member' }, { name: 'author', level: 2, label: 'Author' }, { name: 'editor', level: 3, label: 'Editor' }, { name: 'admin', level: 4, label: 'Administrator' }, { name: 'super_admin', level: 5, label: 'Super Administrator' }, ], coreRoles: ['member', 'admin', 'super_admin'],}export const roleConfig: RoleConfig = { roles: [ { name: 'member', level: 1, label: 'Member' }, { name: 'viewer', level: 2, label: 'Viewer' }, { name: 'contributor', level: 3, label: 'Contributor' }, { name: 'manager', level: 4, label: 'Manager' }, { name: 'admin', level: 5, label: 'Administrator' }, { name: 'super_admin', level: 6, label: 'Super Administrator' }, ], coreRoles: ['member', 'admin', 'super_admin'],}export const roleConfig: RoleConfig = { roles: [ { name: 'member', level: 1, label: 'Member' }, { name: 'moderator', level: 2, label: 'Moderator' }, { name: 'curator', level: 3, label: 'Curator' }, { name: 'volunteer', level: 4, label: 'Volunteer' }, { name: 'admin', level: 5, label: 'Administrator' }, { name: 'super_admin', level: 6, label: 'Super Administrator' }, ], coreRoles: ['member', 'admin', 'super_admin'],}src/types/generated-roles.ts exports:
// Union type of all role namesexport type UserRole = 'member' | 'admin' | 'super_admin'
// Array of all valid rolesexport const USER_ROLES: UserRole[] = ['member', 'admin', 'super_admin']
// Hierarchy levels for each roleexport const ROLE_HIERARCHY: Record<UserRole, number> = { member: 1, admin: 2, super_admin: 3,}
// Human-readable labelsexport const ROLE_LABELS: Record<UserRole, string> = { member: 'Member', admin: 'Administrator', super_admin: 'Super Administrator',}PostgreSQL migration (scripts/migrations/00X_user_roles.sql):
user_role ENUM typeRollback migration (scripts/migrations/rollback_00X_user_roles.sql):
user_role ENUM typeimport type { UserRole } from '#utils/role-types'import { ROLE_HIERARCHY } from '#types/generated-roles'
function canEditPost(userRole: UserRole): boolean { return ROLE_HIERARCHY[userRole] >= ROLE_HIERARCHY.admin}import { ROLE_LABELS } from '#types/generated-roles'
function UserRoleBadge({ role }: { role: UserRole }) { return <span className="badge">{ROLE_LABELS[role]}</span>}import { USER_ROLES } from '#types/generated-roles'
function isValidRole(role: string): role is UserRole { return USER_ROLES.includes(role as UserRole)}config/roles.config.ts and add the rolenpm run setup:roles to regenerate typesnpm run db:migrate to update databaseconfig/roles.config.ts to remove the rolenpm run setup:rolesconfig/roles.config.ts and change levelsnpm run setup:rolesCheck error messages for specific issues:
The setup script auto-increments migration numbers. If you see conflicts:
scripts/migrations/ for existing migrationsnpm run setup:rolesnpm run type-check to see specific errorsrm -rf .astro# Generate types and migrations (interactive)npm run setup:roles
# Dry run (preview changes without writing files)npm run setup:roles:dry-run
# Validate configuration onlynpm run validate:roles
# Apply database migrationnpm run db:migrate
# Check migration statusnpm run db:migrate:statusThe configurable role system has:
member, admin, super_admin) are protectedFor runtime custom roles, see the alternative custom-role-system proposal.