Permissions and roles
Declare App permissions and enforce them with Resource scope, policies, and tenant-managed grants.
Permissions and roles
Protect an App operation
Keep the generated permission declarations, middleware, scoped query, and Policy. For a new business action, add its permission and authorize the action and record in the backend before changing state. A hidden button only changes the UI.
| Where | Responsibility |
|---|---|
| Resource Module | Declare the App's action keys and authorization contract |
| Route middleware | Establish the required installation and operation gate |
| List query | Return only records the actor may see |
| Policy or command | Check permission, record scope, and business state |
| Frontend | Present available actions and handle authoritative API results |
| Tenant access administration | Create a Role and assign an applicable Access Grant |
Add a protected API supplies the implementation procedure. HTTP middleware provides exact route aliases and arguments.
Understand the decision
| Part | Answers | Owner |
|---|---|---|
| Permission | Which operation? | App or Core capability definition |
| Role | Which permissions travel together? | Tenant administration or a Core baseline |
| Grant scope | Where does this assignment apply? | Access Grant |
| Subject population | Whose records may it reach? | Access Grant and the Resource's supported policy |
All applicable conditions must match. A permission definition does not assign it to anyone. App installation makes definitions available; it does not create a business grant. Legal Entity membership, Operating Unit membership, and a selected workspace are also not grants.
Permission keys describe actions, such as workshop.note.update. Do not encode
an organization or population into the key. A tenant can then assign the same
capability at different supported scopes without changing your App.
Use the generated Policy
The Note Policy generated from stubs/package-resource/policy.stub checks an
action and its record boundary together. This is an excerpt inside that Policy,
where the imports and EvaluatesPermissionDecision trait are already generated:
public function update(Actor $user, Note $note): bool
{
return $this->allowsPermission($user, 'workshop.note.update')
&& $this->matchesAuthorizationContract($note);
}matchesAuthorizationContract() calls the SDK's
ResourceAuthorization::recordMatches(). Lists use scopeVisible(); creation
uses the corresponding owner attributes. See Tenant and context
for their responsibilities. Add domain-specific state rules without removing
these checks. Generated deletion remains denied until the App explicitly
implements its deletion lifecycle.
Scope and population
Grant scopes include Tenant, Legal Entity, and Operating Unit. Descendant
coverage belongs to Operating Unit grants. Populations such as all, self,
direct_reports, legal_entity, and operating_unit narrow an otherwise valid
grant; support depends on the Resource's authorization contract. A custom
Resource must implement the corresponding record predicate.
Roles grouped in one Grant share scope, population, validity, and revocation. Use separate assignments when those lifecycles differ. Protected Role assignment requires approval before it becomes a grant; a Role preset is only a suggested bundle. Role Presets explains App contributions.
Check both allowed and denied behavior
Exercise the API directly with an authorized actor and one without the needed grant. Test records outside the actor's allowed scope and any invalid business state. UI visibility alone is not evidence that authorization works.
If installation succeeds but the screen stays hidden, inspect the grant before changing the Policy. Fix tenant context problems and Test an App show the next checks.