Can
Render content only when the principal holds the required permissions or roles, with ALL/ANY matching, a fallback and a loading placeholder.
Can renders its children only when the current principal passes a permission and role check. It
reads the principal from the nearest
AccessControlProvider and throws if there is
none.
Use it for markup: buttons, menu items, page sections. When you need the result as a boolean — to
filter a list, disable instead of hide, or check access in an event handler — use
useAccessControl instead.
Import
import { Can } from "@piplup/react-acl";Usage
Toggle permissions and the role to see ALL, ANY, a wildcard grant and a combined role-and-permission
check respond. Tick invoice:* on its own to watch one wildcard satisfy invoice:line:edit.
"use client";import * as React from "react";import Alert from "@mui/material/Alert";import Checkbox from "@mui/material/Checkbox";import FormControlLabel from "@mui/material/FormControlLabel";import Stack from "@mui/material/Stack";import { AccessControlProvider, Can } from "@piplup/react-acl";const grantable = ["invoice:read", "invoice:write", "invoice:*"];export default function Page() { const [permissions, setPermissions] = React.useState<string[]>(["invoice:read"]); const [isAdmin, setIsAdmin] = React.useState(false); const principal = React.useMemo( () => ({ permissions, roles: isAdmin ? ["admin"] : ["member"] }), [permissions, isAdmin], ); const toggle = (permission: string) => setPermissions((current) => current.includes(permission) ? current.filter((p) => p !== permission) : [...current, permission], ); return ( <Stack spacing={2}> <Stack direction="row" sx={{ flexWrap: "wrap" }}> {grantable.map((permission) => ( <FormControlLabel key={permission} label={permission} control={ <Checkbox checked={permissions.includes(permission)} onChange={() => toggle(permission)} /> } /> ))} <FormControlLabel label="admin role" control={<Checkbox checked={isAdmin} onChange={(e) => setIsAdmin(e.target.checked)} />} /> </Stack> <AccessControlProvider principal={principal}> <Stack spacing={1}> {/* Default mode is ALL: every listed permission is required. */} <Can permission={["invoice:read", "invoice:write"]} fallback={<Alert severity="error">ALL: needs read and write</Alert>} > <Alert severity="success">ALL: can edit invoices</Alert> </Can> {/* ANY: one matching permission is enough. */} <Can mode="ANY" permission={["invoice:read", "invoice:write"]} fallback={<Alert severity="error">ANY: no invoice permission</Alert>} > <Alert severity="success">ANY: can open the invoices page</Alert> </Can> {/* Tick `invoice:*` on its own to see one wildcard satisfy a deeper name. */} <Can permission="invoice:line:edit" fallback={<Alert severity="error">invoice:line:edit: denied</Alert>} > <Alert severity="success">invoice:line:edit: granted by invoice:*</Alert> </Can> {/* Role and permission are checked separately and both must pass. */} <Can permission="invoice:write" role="admin" fallback={<Alert severity="error">admin + invoice:write: denied</Alert>} > <Alert severity="success">admin + invoice:write: can void invoices</Alert> </Can> </Stack> </AccessControlProvider> </Stack> );}What it renders
Can renders exactly one of its three slots, checked in this order:
| Provider state | Renders | Default |
|---|---|---|
loading is true | loading | nothing |
| Check fails | fallback | nothing |
| Check passes | children | — |
No wrapper element is added around any of them.
How access is decided
permission and role are checked separately, and both must pass.
| Requirement | ALL | ANY |
|---|---|---|
Prop omitted or [] | Passes | Passes |
"a" | Principal holds a | Principal holds a |
["a", "b"] | Holds a and b | Holds a or b |
permission defaults to ALL. role defaults to ANY. mode overrides both:
<Can mode="ANY" permission={["post:publish", "post:schedule"]} role={["editor", "owner"]}>
{/* editor OR owner, AND post:publish OR post:schedule */}
<PublishMenu />
</Can>A few consequences:
<Can>with nopermissionand norolerenders its children for every loaded principal.role="admin"with nopermissionchecks only the role.permission="invoice:write"withrole="admin"requires both. To allow either one, use twoCanblocks oruseAccessControl.- Permissions resolve wildcards; roles are compared exactly. See Permissions.
Can hides UI. It does not protect data. Anything the principal must not access also needs a
check on your server.
withCan
withCan wraps a whole component instead of a piece of markup. It takes the same options as Can,
typed as Omit<CanProps, "children">:
import { withCan } from "@piplup/react-acl";
const BillingPanel = withCan(Panel, {
permission: ["billing:read", "billing:write"],
mode: "ANY",
role: "admin",
loading: <Skeleton />,
fallback: <NoAccess />,
});A single permission can be passed directly, with an optional fallback as the third argument:
const AdminPanel = withCan(Panel, "admin:manage");
const Guarded = withCan(Panel, "admin:manage", <NoAccess />);The returned component forwards its own props through to the wrapped one and is named
withCan(Panel) in React DevTools.
Props
Prop
Type