Skip to content
Concept

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.

WhereResponsibility
Resource ModuleDeclare the App's action keys and authorization contract
Route middlewareEstablish the required installation and operation gate
List queryReturn only records the actor may see
Policy or commandCheck permission, record scope, and business state
FrontendPresent available actions and handle authoritative API results
Tenant access administrationCreate 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

PartAnswersOwner
PermissionWhich operation?App or Core capability definition
RoleWhich permissions travel together?Tenant administration or a Core baseline
Grant scopeWhere does this assignment apply?Access Grant
Subject populationWhose 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:

Code example
PHP
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.

Source of truth: docs/developers/content/en/architecture/permissions-and-roles.md