Middleware-enforced roles on every route. System roles + custom roles.
No manual auth checks scattered through your code. Roles are declared, enforced by middleware, and visible in the admin dashboard. You define who can access what — the framework enforces it.
Roles are defined in the vibe_app.roles table — some are system roles (immutable, like vibe_app_admin), others are custom roles you create for your app.
Users are assigned roles via the vibe_app.user_roles table (or the admin dashboard). Role assignments include who assigned them and when — for audit purposes.
Every request passes through middleware that reads the user's roles from their JWT/session. If the route requires a role the user doesn't have, they get a 403.
The page_permissions table maps route patterns to required roles. You can also require 2FA for specific routes. This is declarative — no if-checks in your handlers.
System roles are built into the platform and can't be deleted or renamed: - vibe_app_system_admin — full platform access, can manage other admins - vibe_app_admin — application admin, can manage users and settings - custom_schemas_admin — can create and modify custom schema tables Custom roles are yours. Create "editor", "billing_manager", "support_agent" — whatever your app needs. They're stored in vibe_app.roles and work identically to system roles in middleware checks.
// Role definitions in vibe_app.roles
// System roles (is_system = true, immutable)
vibe_app_system_admin
vibe_app_admin
custom_schemas_admin
// Your custom roles (is_system = false)
editor
billing_manager
support_agent
viewerThe page_permissions table lets you declare which roles can access which routes — and whether 2FA is required. This is checked by middleware before your handler runs. You never write: if (!user.roles.includes("admin")) return 403. The middleware does it. Your handler only runs if the user is authorized.
-- page_permissions table
INSERT INTO vibe_app.page_permissions
(route_pattern, required_roles, requires_2fa)
VALUES
('/admin/*', '{"vibe_app_admin"}', true),
('/billing/*', '{"billing_manager"}', true),
('/editor/*', '{"editor"}', false),
('/api/admin/*','{"vibe_app_admin"}', false);
-- Wildcards match nested routes
-- Multiple roles = user needs ANY of them (OR logic)The MVP includes role-aware navigation components. The admin sidebar only appears if the user has admin roles. Tab bars show/hide items based on roles. The avatar dropdown menu adapts. This isn't just hiding elements — the middleware enforces it server-side. Even if someone manually navigates to /admin, the middleware blocks the request before the page renders.
The admin-roles.ts module exports typed helpers for role checking in your server-side code. These are useful for fine-grained authorization within handlers — when you need more than route-level checks.
import {
SYSTEM_ADMIN_ROLES,
SCHEMAS_ADMIN_ROLES,
ALL_ADMIN_ROLES,
hasSystemAdminRole,
hasSchemasAdminRole,
} from '@/lib/admin-roles';
// In an API route handler
export async function POST(req: Request) {
const session = await getSession();
if (!hasSystemAdminRole(session.roles)) {
return Response.json(
{ error: 'System admin required' },
{ status: 403 }
);
}
// Only system admins reach this point
}Every role assignment and revocation is logged in vibe_app.audit_log with: who made the change, what role was added/removed, which user was affected, and a timestamp. This means you can answer: "Who gave Jane admin access?" and "When was the editor role revoked from user 42?" — without any custom logging code.
Role-Based Access Control ships with every @payez/next-mvp install. 10K MAU free. No credit card required.