Skip to content

Frequently Asked Questions

Short answers with pointers into the guides. New to v3? See What’s New and Migrating from v2.

The selective restriction of access to a resource. AccessControl models who is acting (roles), what they’re doing (actions), what they act on (resources and their attributes), and decides whether it’s allowed — optionally constrained by conditions, ownership, and mandatory gates. It merges RBAC and ABAC.

What’s an “Action”? A “Resource”?

Section titled “What’s an “Action”? A “Resource”?”

An action is the operation performed (the CRUD verbs, or any custom action). A resource is a uniquely named thing being accessed; what counts as a distinct resource is a design decision. See Resources.

Do I Still Have to Check Ownership Myself?

Section titled “Do I Still Have to Check Ownership Myself?”

No — that’s the big v3 change. Tell AccessControl how ownership is determined once (policy.ownerField or policy.owner), pass the record in the check context, and own is enforced. See Ownership. With no resolver configured, own keeps its v2 behavior, so existing code isn’t silently locked down.

.where() is a conditional grant — it can only add access under a condition. .require() is a mandatory gate — independent of grants, it can only restrict. See Conditions and Require Gates.

If a condition reads it with $., it’s context; if the engine reads it to decide behavior, it’s policy. See Best Practices › policy vs context.

Declarative checks are synchronous. A grant/gate that uses a custom function ({ fn, args }) requires the async path (grantedAsync / checkAsync). See Async & Custom Functions.

Yes — it’s storage‑agnostic. Persist the model as flat rows (getGrantsList()) and rehydrate it; the round‑trip is exact. See Serialization & Databases.

How Do I Catch Typos (Unknown Roles/Actions/Resources)?

Section titled “How Do I Catch Typos (Unknown Roles/Actions/Resources)?”

Turn on strict mode. roles is on by default; enable actions/resources to throw on unknown names instead of silently denying.

Subscribe to the access event — it fires on every resolved check (granted and denied) with a denial reason. See Events & Auditing.

A thrown AccessControlError means a fault (usually a misconfiguration), not a normal “denied” — denials return granted: false. Never let a thrown error fall through to “allow”.

  • On the request path, use tryCan(): it never throws — every failure resolves to granted: false.
  • In development/tests, use can() so a typo throws loudly.
  • Branch on the stable err.code (detect with AccessControl.isACError(err)), not on message text.

See Security Considerations for the full hardening story.

Single pinned runtime dependency, zero production advisories (npm audit --omit=dev), 100% coverage, mutation‑tested, plus an adversarial suite and a property fuzzer. See Best Practices › Quality & testing.