On this page
Overview
Laravel React Permissions brings the idea of Blade’s @can to React and Inertia. Your Laravel app shares the signed-in user’s permission names as an Inertia prop; a Can component, a withPermission wrapper and a usePermissions hook then decide what to render.
Permission checks support exact names, wildcards such as users.*, and boolean expressions such as (users.* || posts.*) && admin.access. The package has no runtime dependencies beyond React and Inertia, and is MIT licensed and maintained by the DevWizard core team.
These checks shape the interface only. Always enforce permissions on the server as well.
Features
Cancomponent for conditional rendering with afallback.withPermissionhigher-order component that gates a whole component.usePermissionshook with exact, any, all, pattern and expression checks.- Wildcards:
*matches any run of characters and?matches one. - Boolean expressions with
||,&&,|,&, parentheses and the literalstrueandfalse. - Override the permission list per call with a
permissionsarray. - Authentication requirement, on by default.
- Exported types for the shared props, hook return and component props.
- Tree-shakeable ESM build; works with the permission names produced by packages such as Spatie Laravel Permission.
Requirements
- React 18 or 19.
@inertiajs/react1 or 2.- A Laravel backend that shares
auth.user.permissionsas an array of strings.
Installation
pnpm add @devwizard/laravel-react-permissionsOther package managers:
npm install @devwizard/laravel-react-permissions
yarn add @devwizard/laravel-react-permissionsConfiguration
There is no provider and no config file. Share the permissions from your Inertia middleware, under auth.user.permissions:
public function share(Request $request): array
{
return [
...parent::share($request),
'auth' => [
'user' => $request->user() ? [
'id' => $request->user()->id,
'name' => $request->user()->name,
'permissions' => $request->user()->getAllPermissions()->pluck('name')->toArray(),
] : null,
],
];
}getAllPermissions() comes from Spatie Laravel Permission; any source that produces an array of permission names works.
Usage
Wrap UI in Can:
import { Can } from '@devwizard/laravel-react-permissions';
export function Toolbar() {
return (
<Can permission="users.create" fallback={<span>No access</span>}>
<button>Create user</button>
</Can>
);
}Read the same checks in code with the hook:
import { usePermissions } from '@devwizard/laravel-react-permissions';
export function Menu() {
const { hasPermission, isAuthenticated } = usePermissions();
if (!isAuthenticated) return null;
return hasPermission('reports.*') ? <ReportsLink /> : null;
}Gate a whole component:
import { withPermission } from '@devwizard/laravel-react-permissions';
export const AdminPanel = withPermission(Panel, {
expression: '(admin.* || manager.*) && active.user',
fallback: <p>Admin access required</p>,
});API Reference
Can props
When several checking props are given, the first one present wins, in this order: expression, permission, anyPermissions, allPermissions, anyPatterns, allPatterns, pattern.
| Prop | Type | Description |
|---|---|---|
permission |
string |
Exact name, wildcard or expression |
expression |
string |
Boolean expression |
anyPermissions |
string[] |
Passes if any entry matches exactly |
allPermissions |
string[] |
Passes if every entry matches exactly |
anyPatterns |
string[] |
Passes if any pattern or expression passes |
allPatterns |
string[] |
Passes if every pattern or expression passes |
pattern |
string |
Single pattern, kept for older code |
permissions |
string[] |
Use this list instead of the shared one; an empty array means none |
fallback |
ReactNode |
Rendered when access is denied |
requireAuth |
boolean |
Deny unauthenticated users; default true |
children |
ReactNode |
Rendered when access is granted |
With requireAuth set to false and no checking prop, Can renders its children.
withPermission(Component, options)
Takes the same options as Can, except children, and returns a wrapped component with a withPermission(Name) display name.
usePermissions(permissions?)
| Member | Description |
|---|---|
userPermissions |
The active list: the argument if given, else auth.user.permissions, else empty |
hasPermission(permission) |
Exact name, or a pattern or expression when it contains wildcards or operators |
hasAnyPermission(list) |
True if any entry is in the list; exact names only |
hasAllPermissions(list) |
True if every entry is in the list; exact names only |
hasPermissionPattern(pattern) |
Pattern or expression check |
hasAnyPattern(patterns) |
Any pattern passes |
hasAllPatterns(patterns) |
All patterns pass |
getMatchingPermissions(pattern) |
The user’s permissions that match a wildcard pattern |
checkExpression(expression) |
Evaluate a boolean expression |
isValidExpression(expression) |
Whether an expression is syntactically valid |
isAuthenticated |
True when auth.user exists |
Types
Auth, SharedData, User, NavItem, PermissionProps, CanProps, UsePermissionsReturn and WithPermissionOptions.
Examples
Show an action if the user can do anything with posts, or manage the site:
<Can permission="posts.* || site.manage">
<NewPostButton />
</Can>Filter a navigation list:
const { hasPermission } = usePermissions();
const visible = items.filter((item) => !item.permission || hasPermission(item.permission));List the permissions behind a wildcard:
const { getMatchingPermissions } = usePermissions();
getMatchingPermissions('users.*');Test a component with a fixed permission list:
<Can permission="admin.access" permissions={['admin.access']}>
<AdminLink />
</Can>FAQ & Troubleshooting
Everything is hidden. Check that the page props contain auth.user.permissions as an array of strings. Can denies by default for signed-out users while requireAuth is on.
anyPermissions ignores my wildcard. It compares exact names. Use anyPatterns, or put the alternatives in one permission expression with ||.
Is this secure? No client-side check is. The permission list is visible in the page data, and hiding a button does not protect an endpoint. Authorize every request in Laravel with policies or middleware.
My expression always returns false. Use isValidExpression to test it, and check for a typo in a permission name. Invalid expressions log a warning and evaluate to false.
Can I use permissions from somewhere other than Inertia? Yes. Pass permissions to Can or to usePermissions and the shared list is ignored. The hook still calls Inertia’s usePage, so use it under an Inertia app.
Changelog & Support
Release history is in the CHANGELOG and on the GitHub releases page. Report bugs and request features in GitHub issues. Source: DevWizardHQ/laravel-react-permissions. For help with Laravel and React products, the DevWizard team takes project contracts and monthly retainers through the contact page.