Check functions
hasPermission, hasRole, hasAllPermissions, hasAnyPermissions and createGetRolePermissionAccess — the same checks without React.
Every check in this package is a plain function over a principal. They need no context and no component, so the same rules apply in a navigation filter, a route loader, a server action, a Node server or a test as in your markup.
import { hasPermission } from "@piplup/react-acl";
const visible = items.filter((item) => hasPermission(user, item.requiredPermissions));Inside components prefer Can and
useAccessControl — they read the principal for you and
suspend checks while it loads, which these functions cannot do.
In React Server Components
From 4.1.0 the check functions also run in React Server Components code: Next.js App Router server
components, server actions and route handlers. Only AccessControlProvider, useAccessControl,
Can and withCan are marked "use client", and everything still comes from @piplup/react-acl.
import { hasPermission } from "@piplup/react-acl";
export async function GET() {
const principal = await loadAccess();
if (!hasPermission(principal, "report:read")) {
return new Response(null, { status: 403 });
}
// ...return the reports
}In 4.0.0 the whole package was marked "use client", so these calls throw "Attempted to call
hasPermission() from the server but hasPermission is on the client". Upgrade to 4.1.0 or later.
Servers that don't use React Server Components, such as Express, work with either version.
hasPermission
hasPermission(
principal: AccessPrincipal,
requiredPermissions: Permission | readonly Permission[],
matchMode?: "ALL" | "ANY",
): boolean;Defaults to ALL. Resolves wildcards held by the principal,
so report:* satisfies report:summary:monthly. A null or undefined principal is denied, and an
empty requirement passes.
hasPermission({ permissions: ["report:*"] }, "report:read"); // true
hasPermission({ permissions: ["report:read"] }, ["report:read", "report:write"]); // false
hasPermission({ permissions: ["report:read"] }, ["report:read", "report:write"], "ANY"); // true
hasPermission(null, "report:read"); // false
hasPermission(null, []); // true — nothing was requiredhasRole
hasRole(
principal: AccessPrincipal,
role: Role | readonly Role[],
matchMode?: "ALL" | "ANY",
): boolean;Defaults to ANY, the opposite of hasPermission — asking for one of several roles is the common
case, while asking for several permissions at once usually means all of them. Roles are compared
exactly and never resolve wildcards.
hasRole({ roles: ["editor"] }, ["admin", "editor"]); // true
hasRole({ roles: ["editor"] }, ["admin", "editor"], "ALL"); // false
hasRole({ roles: ["*"] }, "admin"); // false — roles are not hierarchicalhasAllPermissions and hasAnyPermissions
hasAllPermissions(
grantedPermissions: readonly string[] | undefined,
requiredPermissions: Permission | readonly Permission[],
): boolean;
hasAnyPermissions(
grantedPermissions: readonly string[] | undefined,
requiredPermissions: Permission | readonly Permission[],
): boolean;The two halves of hasPermission, taking a permission list rather than a principal. Use them when
you hold the permissions on their own — a token claim, a role record — and have no principal object
to wrap them in. An empty requirement passes in both.
createGetRolePermissionAccess
createGetRolePermissionAccess(
assignablePermissions: AssignablePermission[],
): (permissionSlugs: readonly string[]) => RolePermissionAccess;Summarises how much of your permission set a role covers, for an admin screen that lists roles. Pass the full assignable set once; call the result with each role's permissions.
const getAccess = createGetRolePermissionAccess(["invoice:read", "invoice:write", "user:read"]);
getAccess(["*"]); // { kind: "wildcard", label: "Wildcard access", … }
getAccess(["invoice:read", "invoice:write", "user:read"]); // { kind: "all", label: "All permissions", … }
getAccess(["invoice:read"]); // { kind: "partial", label: "1 of 3 permissions", … }
getAccess([]); // { kind: "none", label: "No permissions", … }Each result carries a kind to branch on, plus a label and description you can render directly.
Slugs that are not in the assignable set are ignored, so a stale permission on a role does not
inflate the count.