canAny answers a different question from can: not “does this principal hold this specific permission here?” but “does this principal hold anything under this subtree at all?” That distinction makes canAny the right tool for show/hide decisions in navigation and conditional UI, and the wrong tool for anything that authorizes an action. The static verifier (@alfiz/verify) produces a build error if it detects canAny in a server action or route handler, treating it as a gate.
canAny(principal, pattern)
PrincipalRef
required
Identifies who is being checked. Either
{ userId: string } for a human
user or { serviceId: string } for a machine principal.P
required
A permission pattern declared in your catalog. Three shapes are valid:
- A concrete key such as
"docs.files.read"matches exactly that key. - A subtree wildcard such as
"docs.*"or"admin.*"matches every key under the named group, at any depth. - The bare
"*"matches every key in the catalog.
PatternOf<typeof catalog> union at literal call sites. At runtime, an
unknown pattern raises UnknownPermissionError.Return value
Promise<boolean>
Resolves to
true when the principal holds at least one unexpired,
non-revoked grant whose effective permission set intersects the pattern.
Resolves to false for inactive principals, or when no matching grant
exists at any scope. This method never throws for a denial.requireAny(principal, pattern)
canAny. Throws AccessDeniedError with reason: "forbidden" when no matching permission is held, always that reason, including for an inactive principal, whose check simply evaluates to false before any row is read. Use this for project-root visibility checks, for example to guard the project settings page against users with no access to the project at all, without requiring a specific leaf key.
Visibility affordance, never a gate
canAny and can answer complementary questions:
When you use
canAny as a gate, you admit any user who holds any permission in the subtree, including, for example, a user who can only delete documents but cannot read them. A grant of docs.files.delete at one specific document would pass canAny(p, "docs.*"), but that user must not be able to call a GET /docs/:id route for arbitrary documents.
Pattern syntax
Patterns are the typed string unionPatternOf<typeof catalog>, which includes:
- Every concrete permission key in the catalog (
"docs.files.read","docs.files.update_file", …) - Every group-level wildcard (
"docs.*","docs.files.*","admin.*", …) - The global wildcard
"*"
.* suffix are not valid patterns. "docs.files" is a group name and not a pattern. The catalog rejects unknown patterns with a suggestion: if you pass "docs.files", the error will recommend "docs.files.*".
The correct pattern: canAny for nav, can for the gate
Showing a project card
Showing a settings gear icon
requireAny for project-root access
canAny uses the same subject-closure cache as can. You do not pay an
extra provider round-trip for canAny calls that follow a can call for
the same principal within the same subjectCacheTtlMs window. Use
client.snapshot(principal) when you need both in the same render to
ensure they operate over one consistent data instant.