Skip to main content
The catalog carries the navigation structure your application renders alongside the permissions themselves. By declaring menu items alongside the permissions that govern them, you give alfiz-verify everything it needs to confirm that nav visibility and page-level gates are consistent, and you keep the permission-to-UI mapping in one place instead of scattered across layout files.

How navigation visibility works

Navigation visibility is evaluated by canAny(). When a user loads your application, each nav item’s permission field is tested against what the user actually holds. Items for which the user holds nothing under the declared pattern simply do not render. This evaluation happens on your server, like every other check. canAny reads your own database through the provider, and the synchronous form is snapshot.canAny, taken once per request. What makes it a display optimization rather than a security gate is what it answers: whether the user holds anything under the pattern. The page the nav item points to must gate itself independently with require on a concrete key.
Navigation visibility is not a security gate. A user who knows a URL can navigate directly to a page regardless of whether the nav item rendered for them. Every page, server action, and route handler must gate itself with require on a concrete key. alfiz-verify errors when it finds canAny() used as a gate in a server action or route handler.
string
required
The text shown in the navigation menu. Also used as the path identifier in lint error messages, so keep it unique and descriptive.
string
The URL this nav item links to. Optional on parent items that are purely containers for children.
PermissionPattern | readonly PermissionKey[]
The visibility condition. Accepts three forms:
  • A concrete key, such as "docs.files.read". The item renders when the user holds exactly this permission.
  • An array of keys, such as ["docs.files.read", "docs.files.update_file"]. The item renders when the user holds any key in the list.
  • A wildcard pattern, such as "docs.*" or "docs.files.*". The item renders when the user holds any permission under the pattern. This is the most common form for top-level nav sections.
alfiz-verify checks that every value here resolves against the catalog, so unknown keys and patterns that match nothing are lint errors.
readonly NavItemInput[]
Nested nav items. Child visibility is evaluated independently, so a child can render even when its parent pattern would not match, though in practice parent items are almost always broader patterns that cover their children.

canAny vs. can

can is a gate. It returns a yes/no decision about a concrete, enumerated permission at a specific scope. Use it at every enforcement point: page guards, server actions, and route handlers.canAny is a visibility affordance. It returns true when the user holds anything under a pattern. It does not name what they can do, only whether there is something. Use it exclusively for rendering decisions: show this nav section, show this sidebar category, show this menu group.The rule is architectural: can is provable at a specific path in your codebase; canAny is not. alfiz-verify enforces this distinction by erroring when canAny appears in a server action or route handler.

Expressing “show if the user holds anything under a group”

The most common pattern for sidebar sections is a wildcard over the project or tab:

A complete multi-section navigation example

Keep parent nav items’ permission patterns broad enough to cover all their children. If a user holds docs.files.create but not docs.files.read, a parent item gated on docs.files.* will still render, and the children will show only the items the user actually holds. This is the intended behavior.

What alfiz-verify checks

When you run alfiz-verify, it lints every permission value in your navigation:
  • Unknown key or pattern. A nav permission that doesn’t appear in the catalog is an error. This catches typos and references to permissions you renamed.
  • Pattern matching no keys. A wildcard that currently matches nothing is a warning (the catalog may be growing and the pattern is intentional, but you should confirm).
  • canAny used as a gate. Any file where canAny guards a server action or exported handler is an error.
These checks mean you can refactor permissions confidently: rename a tab, and the verifier immediately surfaces every nav entry that still references the old name.