Add the Users Module
A ready-made access-control admin surface installed via the Buildpad CLI (Copy & Own — the code is copied into your project). It manages three interdependent domains — users, roles, and policies — on top of the DaaS access-control APIs, so you never hand-build user tables, role editors, or permission matrices.
Six screens ship together:
UsersManager— the/userslist: search, role/status filters, pagination, permission-gated create/edit/delete.UserDetail— the/users/[id]page: profile fields, password rules, role assignment, API token generation/management, and a Policies tab.RolesManager— the/roleslist with member counts.RoleDetail— the/roles/[id]page: icon, parent role hierarchy, scope-assignment rules, plus Users and Policies tabs.PoliciesManager— the/policieslist with user/role attachment counts.PolicyDetail— the/policies/[id]page: access flags (app_access,admin_access,delegate_access) and a per-collection permissions matrix with a custom-permission editor.
CRITICAL: Never Create These Manually
Like all Buildpad UI, the Users module is CLI-installed and owned as source. Do not hand-write components/ui/users-management/* or the useUsers/useRoles/usePolicies/useAccess hooks in lib/buildpad/hooks/ — scaffold them. For app-level role gating, do not invent role-name checks either — see grant-module-access.
Prerequisites Check
node --version && pnpm --version && npx --version
Requires Node.js v24 LTS and pnpm v10+ (see add-buildpad for install guidance). The module must render under the authenticated layout (buildpad init generates app/(authenticated)/ with a DaaSProviderWrapper) so DaaSProvider header injection is in scope — the hooks call your app/api/users/*, app/api/roles/*, app/api/policies/*, app/api/permissions/*, and app/api/collections/* proxy routes.
DaaS CORS must be configured first. The managers fetch authenticated DaaS endpoints from the browser with credentials: 'include'. DaaS's default cors_origins: ["*"] is incompatible with credentialed requests — the browser blocks every preflight. Set explicit origins before mounting the module (see daas-platform "CORS must use explicit origins"). This is not Users-specific: it affects every authenticated Buildpad component.
Installation
The Users module is opt-in — it is not part of bootstrap.
# 1. Add the module (copies app/users/, app/roles/, app/policies/ shells and
# components/ui/users-management/, plus the access-control hooks)
npx @buildpad/cli@latest add users-routes --cwd /path/to/project
# 2. Add the DaaS proxy routes it needs (if not already present)
npx @buildpad/cli@latest add api-routes --cwd /path/to/project
The docs use the shorthand
buildpad add users-routeswhen the CLI is installed globally;npx @buildpad/cli@latest add …is the equivalent no-install form used across these skills.
If the CLI command fails,
@buildpad/cliIS published to npm — verify withnpm view @buildpad/cli versionbefore assuming otherwise. The CLI fetches its registry fromraw.githubusercontent.com, so confirm the environment can reach both npm and the GitHub raw CDN.
The Routes
app/users/
├── page.tsx # /users — UsersManager list
└── [id]/page.tsx # /users/[id] — UserDetail profile + tokens + policies
app/roles/
├── page.tsx # /roles — RolesManager list
└── [id]/page.tsx # /roles/[id] — RoleDetail hierarchy + members + policies
app/policies/
├── page.tsx # /policies — PoliciesManager list
└── [id]/page.tsx # /policies/[id] — PolicyDetail flags + permissions matrix
Usage
List view — UsersManager
// app/users/page.tsx
"use client";
import { useRouter } from "next/navigation";
import { UsersManager } from "@/components/ui/users-management";
export default function UsersPage() {
const router = useRouter();
return (
<UsersManager
=> router.push(`/users/${user.id}`)}
=> router.push("/users/new")}
/>
);
}
| Prop | Purpose |
|---|---|
onUserClick |
Open a user (e.g. navigate to the detail page) |
onCreateUser |
Handle the permission-gated create action |
pageSize / pageSizeOptions |
Pagination control |
hideHeader |
Embed the list without its own header chrome |
usersCollection |
DaaS collection used for RBAC checks |
Detail view — UserDetail
// app/users/[id]/page.tsx
"use client";
import { use } from "react";
import { useRouter } from "next/navigation";
import { UserDetail } from "@/components/ui/users-management";
export default function UserDetailPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = use(params);
const router = useRouter();
return (
<UserDetail
id={id}
=> router.push("/users")}
=> router.push("/users")}
=> router.push(`/policies/${policy.id}`)}
/>
);
}
The roles and policies pages follow the identical pattern: RolesManager/RoleDetail (onRoleClick, onUserClick, onPolicyClick cross-navigation) and PoliciesManager/PolicyDetail. Wire the on*Click callbacks so the three domains link to each other.
Supporting components (reuse anywhere)
SystemPermissions (per-collection CRUD-S matrix), PermissionDetailModal (tabbed custom-permission editor), TokenInput (static token with client-side generation), SelectIcon/IconDisplay, and the shared list chrome (SearchInput, ListFooter, ListEmptyState, RowActionsMenu).
The Data Layer (reuse anywhere)
All six screens are thin wrappers over hooks from @/lib/buildpad/hooks:
import { useUsers, useRoles, usePolicies, useAccess } from "@/lib/buildpad/hooks";
useUsers()→fetchUsers,getUser,createUser,updateUser,deleteUser,bulkUpdateUsers,fetchUserPolicies,attachUserPolicy,detachUserPolicy(+loading,error)useRoles()→ role CRUD plus policy attach/detach, mirroringuseUsersusePolicies()→ policy CRUD; list rows includeuserCount/roleCountuseAccess()→ raw access to thedaas_accessjunction when you need custom attachment logic
Access-Control Semantics (IMPORTANT)
admin_accesson users and roles is computed, never written. To make someone an admin, attach a policy whoseadmin_accessflag is true — do not try to set a field on the user.- Permissions come from policies OR-merged across a user's direct policies and role policies. The
PolicyDetailmatrix is the source of truth per collection. - For application-level capability gating (hiding buttons/pages by capability), use Module-Level Access Keys via grant-module-access — never role-name checks. For data-level permission design, see create-rbac and relational-permissions.
Post-Install Validation
npx @buildpad/cli@latest status --cwd /path/to/project
npx @buildpad/cli@latest validate --cwd /path/to/project
cd /path/to/project && pnpm build
Related
- add-buildpad — the underlying CLI and low-level field widgets.
- create-rbac — designing the roles/policies/permissions model this module administers.
- grant-module-access — app-level capability flags checked with
hasModuleAccess. - manage-scope — multi-tenant scope rules that
RoleDetailscope assignment builds on. - buildpad-reference — full component catalog.