Skip to Content
Piplup
@piplup/react-aclFunctions

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.

app/api/reports/route.ts
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 required

hasRole

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 hierarchical

hasAllPermissions 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.

On this page