Skip to main content
grantedScopes is the database-query primitive for permission-filtered lists. Instead of loading every resource and checking can per row is an O(N) pattern that defeats the purpose of database indexes. You call grantedScopes once, obtain the set of scope instance IDs where the principal holds the key, and push that filter into your query. Alfiz’s companion listing helpers turn those two sets into a query: planListing reduces them to a ListingPlan, one of { mode: "all" }, { mode: "none" }, { mode: "all_except", exclude }, or { mode: "scoped", include, exclude }, and matPathCondition, closureTableCondition, and prismaMatPathWhere build the actual predicate from it.

client.grantedScopes(principal, key)

PrincipalRef
required
Identifies who is being evaluated. Either { userId: string } for a human user or { serviceId: string } for a machine principal.
LooseKey<K>
required
A concrete permission key declared in your catalog. The key is verified against the catalog before evaluation; an unknown key raises UnknownPermissionError. Wildcard patterns are not valid here. This method returns scope sets for one specific leaf.

Return value

Set<ScopeId>
The scopes of the principal’s unexpired grant rows that match key. including grants conferred through a role and through wildcard patterns. For an inactive principal, this set is always empty.
granted is not revoke-filtered. Revokes are returned separately, in revoked, and it is the caller’s job to subtract them. Applying only granted fails open.
These are the scopes the grants were made at, which in a hierarchical deployment are usually ancestors such as docs.folder:42, not the docs.doc:* ids on your rows. Match rows by ancestry (a closure table or a materialized path), not by scope-id equality.
Set<ScopeId>
The set of scope instance IDs at which the principal has a personal revoke that suppresses key. Use this set to exclude rows from queries: a scope in revoked (or any descendant of it) should not appear in the results even if the scope’s id appears in granted through an ancestor grant.

How to use grantedScopes with a listing query

The pattern has three steps:
  1. Call grantedScopes to obtain the granted and revoked scope sets.
  2. Use the sets to build a SQL predicate that filters the resource table.
  3. Execute the query, and the database applies the permission filter, so you load only the rows the principal is allowed to see.
A GIN index on a tsvector column of path segments, or a dedicated doc_ancestors closure table with an index on (ancestor_id), makes the = ANY($1::text[]) filter a single index scan. Without an appropriate index, this degrades to a sequential scan as the result set grows. See the PostgreSQL docs on GIN indexes and closure tables for schema guidance.

client.holds(principal, key)

Returns true if the principal holds key at any scope: the single-key “does this button exist at all?” probe. Use holds for unscoped conditional UI where the concrete scope is not yet known: someone who holds publish_file on only a handful of folders should still see the publish button surface. The button’s server action still gates with can at its concrete scope.
PrincipalRef
required
The principal to check.
LooseKey<K>
required
A concrete permission key in the catalog.

Return value

Promise<boolean>
true when the principal holds an unexpired, non-globally-revoked grant for key at any scope. false for inactive principals or when no grant exists. Never a gate; use can for authorization.
holds is a visibility affordance, not an authorization gate. It answers “should this surface exist at all for this user?” A positive result does not authorize any action; every action behind the button still gates with client.can at the concrete resource scope.

client.heldKeys(principal)

Returns every concrete catalog key the principal holds somewhere, granted by an unexpired row at any scope, suppressed only by a global-scope revoke. This is the full “what can this user do?” list, and it is the right feed for unscoped conditional UI that needs to render multiple surfaces at once.
PrincipalRef
required
The principal to evaluate.

Return value

Promise<PermissionKey[]>
An array of every concrete key the principal holds at some scope. Empty for inactive principals. Order matches the catalog’s key order (alphabetical). O(catalog size), so call once per request and reuse; snapshot(principal).heldKeys does exactly that.

Examples

holds for unscoped conditional UI

heldKeys for a permissions summary page

Using snapshot.grantedScopes synchronously on a list page

Combining with snap.resolve for a hierarchical list page