usePermissions() hook. The registry surfaces — nav entries, settings entries, custom routes — also accept a subject shortcut, and nav entries take a generic if predicate that reads from permissions.
Two layers
UI gating is for UX. Backend authorization is for security. They are not the same:| Layer | Where | What it does |
|---|---|---|
| UI gating | Dashboard registries (subject, if) | Hides menu items, columns, buttons — keeps the interface tidy |
| Authorization | Rails API controllers (CanCanCan, scopes) | Refuses the request when the user doesn’t have permission |
authorize! call is what stops them.
The permissions object
'Spree::Order', 'Spree::Product', or your own 'MyApp::Report'). There is no client-side record-level check — when a rule is conditional on record attributes, isConditional returns true and the API is the arbiter: render the control and handle a possible 403.
subject shortcut
Available on NavEntry, SettingsNavEntry, and RouteEntry. It hides the entry unless the user can read the subject:
subject also renders a 403 page if the user navigates directly:
subject whenever you want a “user can read this resource” check. For anything else, use if.
if predicate (nav entries)
if on a nav entry is the escape hatch. It runs at render with { permissions, store, user } and returns a boolean:
if context types store loosely — cast to the SDK’s Store for typed access, the same way the built-in Getting Started entry does.)
Use it for combined checks (permission + store state), feature flags, multi-action checks (can(update) && can(read)), or anything the subject shortcut can’t express in one string. It combines with subject — both must pass. (Slot entries have an if too, but it receives only the slot’s own context — see Slots.)
Inside components
Use theusePermissions() hook — it returns { permissions, rules, isLoading }:
permissions object backs the nav registry’s if predicate, so you can move logic between the two without changing behaviour. For declarative gating, <Can I="update" a="Spree::Order">…</Can> (also from @spree/dashboard-core) renders children only when the check passes.
Custom abilities
If your customization introduces a new model on the backend, extend the CanCanCan ability there (typically a decorator onSpree::PermissionSets::DefaultCustomer or Spree::PermissionSets::DashboardManagement):
permissions.can('read', 'MyApp::Report') resolves in the dashboard exactly like a first-party check — the abilities ship to the dashboard with the current-user response (GET /api/v3/admin/me) at sign-in.
Reference
Permissionsinterface- CanCanCan docs — the Ruby ability definition language

