
Every library returns true or false. Accessly returns why — matched rules, missing permissions, and the exact source of every decision.
Select a backend, send a request, and trace every decision through the pipeline in real time.
{
"id": 1,
"name": "Alex Admin",
"all_permissions": [
"posts.create",
"posts.delete",
"users.view"
]
}createAdapter((src) => ({
permissions: src.all_permissions,
user: { id: src.id, name: src.name }
})){
"permissions": [
"posts.create",
"posts.delete",
"users.view"
],
"user": { "id": 1, "name": "Alex Admin" }
}engine.check("posts.create")You have permission to create posts. The button is fully interactive.
A transparent pipeline from API response to gated UI — every step is observable.
{ "scopes": ["users.read"] }createAdapter(src => ...){ permissions: [...] }engine.check("users.read"){ allowed: true, reason }import { createAdapter, PermissionProvider, Can } from "accessly";
// 1. Define adapter for your backend
const adapter = createAdapter((src) => ({
permissions: src.user.scopes,
roles: [src.user.role],
user: { id: src.user.userId },
}));
// 2. Wrap your app once
export function App({ apiData }) {
return (
<PermissionProvider source={apiData} adapter={adapter}>
<Dashboard />
</PermissionProvider>
);
}
// 3. Gate any component declaratively
function Dashboard() {
return (
<Can permission="users.create">
<button>Create User</button> {/* only renders if allowed */}
</Can>
);
}
// 4. Or get the full decision object
const decision = useAccessDecision("users.create");
// → { allowed: true, reason: "matched",
// matched: ["users.create"], checkedFrom: "direct" }Authorization decisions affect every user's experience. Accessly tracks every check so you can always answer: why was this allowed or denied?
Production-grade access control. Zero external dependencies.
No more debugging with console logs. Get matched rules, missing permissions, timestamps, and the origin of every decision.
const decision = useAccessDecision("users.create");
// {
// allowed: true,
// reason: "matched",
// matched: ["users.create"],
// checkedFrom: "direct",
// timestamp: "2026-06-29T..."
// }Pass your navigation config. Get back only what the user can see. Recursive, nested, zero boilerplate.
const nav = filterNavigation( allRoutes, engine );
Map roles to permission arrays. Auto-expanded at check time. Source tracked as checkedFrom: "role".
Built-in flag support with the same unified API. Use <Can flag='beta'> just like permissions.
Fully server-side rendering compatible. Hydrates cleanly. Works in React Server Components.
Full types everywhere. ESM + CJS. Tree-shakeable — import only what you use. No peer dependency conflicts.
formatDecision() and inspectAccess() give human-readable diagnostics. Paste into a bug report and it makes sense immediately.
Every API returns permissions differently. Laravel uses arrays. NestJS uses abilities. ASP.NET uses claims. Accessly normalizes all of them into one consistent model.
{
"id": 1,
"name": "Alex Admin",
"all_permissions": [
"posts.create",
"posts.delete",
"users.view"
]
}createAdapter((src) => ({
permissions: src.all_permissions,
user: {
id: src.id,
name: src.name
}
})){
"permissions": [
"posts.create",
"posts.delete",
"users.view"
],
"user": {
"id": 1,
"name": "Alex Admin"
}
}No more guessing why access was denied. Get the full story every time.
{
"allowed": false,
"reason": "missing_permissions",
"requested": ["settings.manage"],
"matched": [],
"missing": ["settings.manage"],
"checkedFrom": "direct",
"timestamp": "2026-06-26T11:39:00.000Z"
}pages.dashboardusers.createsettings.manageNo install required. Test against your backend in the interactive lab — then copy the generated code directly.