Jetstack combines several access-control layers:
That combination is powerful, but it also means access is not determined by a single yes-or-no matrix. The runtime resolves permissions cumulatively and in a specific order.
This is one of the most important governance layers in the platform. It decides:
To design access well, you need to understand not only what permissions exist, but how the runtime combines them.
Keep these layers separate:
That is why "the user has the role" is never the full answer on its own.
Roles define stable capability bundles and availability relationships. In practical terms, a role can influence:
Role order also matters.
role_priorityWhat it is:
How it works:
Why it matters:
This is a key part of the cumulative logic.
Jetstack evaluates permissions in three main stored layers:
These govern broad application capabilities such as:
Use system permissions when the action is not tied to one business type or one property.
These govern actions on a type, such as:
listshowcreateeditdeletefreeCreatefreeEditshowTab.*Use type permissions when the rule is about objects of a type as a whole.
These govern field-level actions such as:
Use property permissions when access must differ at the field level.
The permissions manager supports broad wildcard-style rules through NULL values in storage:
This is important because the runtime looks for matches in a fallback order:
That fallback order is one of the main reasons the permission model feels cumulative.
The actual runtime logic in permissionsManager.php is not "collect all matching permissions and then merge them mathematically." It behaves more like ordered resolution with fallback.
The current user’s roles are sorted by role_priority in ascending order before the permission manager evaluates them.
That means:
This is the first important implementation detail.
Before stored permission tables are consulted, the permissions manager checks built-in implicit capabilities for system roles.
These built-in capabilities can immediately allow access without consulting stored permission rows.
This applies especially to:
SuperuserReaderPublisher sometimes informally referred to as "Published"EditorModel ImplementerThese are described in detail later in this chapter.
If no built-in shortcut applies, the manager checks its caches in fallback order.
For type permissions, the matching order is effectively:
If a match is found:
true means allowedQueryfalse means denied for that checked pathThe first matching result wins.
This is one of the most important implementation details.
The manager stores deny rules separately and uses them mainly to stop fallback from broader rules for the same role and exact target/action combination.
What this means in practice:
So the implementation is cumulative and additive across roles, but not in the sense of "any deny anywhere always overrides everything."
The safer implementation-facing rule is:
If the match is a query-backed allow, the manager returns that Query object.
Important consequence:
So query-scoped permissions are resolved as a single winning rule, not as an intersection or union of all matching permission queries.
If no built-in shortcut and no stored permission match applies, the permission resolver returns false.
Once caches are fully loaded, the manager does not keep probing the database for missing cases during the same resolution pattern. A non-match is treated as a deny.
freeCreate And freeEditThe manager treats freeCreate and freeEdit as privileged actions.
These actions cannot be allowed through the broad wildcard action fallbacks in the same way as ordinary actions.
In practice, this means:
Superuser is still unrestrictedThis is an important guardrail in the implementation.
Property permissions use the same general pattern as type permissions:
QueryProperty permissions have two extra implementation details:
duplicated_from_id special-caseThe manager implicitly allows property action create on fields ending with .duplicated_from_id.
This supports duplication flows without requiring extra explicit property rules for that field.
If the user has the Model Implementer system role and the property belongs to the Type or Property schema types, the manager implicitly allows access through the schema-property shortcut.
This is part of the built-in implementer behavior.
System permissions are simpler.
The manager checks:
As with other layers:
Permission queries are what turn "allowed" into "allowed only for matching objects."
If a permission rule contains a query:
trueWhen a permission query is applied to a database selection, the manager:
where and whereOr conditions into the target selectionThe manager also keeps an internal recursion guard so the same permission query is not re-applied endlessly while already being processed.
This is an important implementation detail when permission queries reference data structures that may themselves trigger permission-aware loading.
In addition to the permissions manager, Jetstack has presenter/resource ACL defined in AuthorizatorFactory.
This gives system roles some baseline application capabilities even before stored permission rows are considered.
UserUser is the base authenticated system role.
It gets baseline access to application infrastructure such as:
HomepageModuleSearchActiveResourceUploadThis is the foundation that the more specific system roles build on.
ReaderReader inherits from User.
Presenter-level implicit capability:
Data areaPermissions-manager implicit capabilities:
listshowshowTab.*This applies to:
ModuleUserGrouptypeId = null in the evaluatorPractical meaning:
Reader is the built-in role for read-oriented access to ordinary data areasPublisherThe codebase defines the role as Publisher. If your internal terminology says "Published," this is the role being referred to.
Publisher inherits from Reader.
Presenter-level implicit capability:
Reader presenter access, including DataPermissions-manager implicit capabilities:
listshowcreateshowTab.*This applies to:
ModuleUserGrouptypeId = nullPractical meaning:
Publisher is a lightweight creator roleEditorEditorEditor inherits from Reader.
Presenter-level implicit capability:
Reader presenter access, including DataPermissions-manager implicit capabilities:
listshowcreatefreeCreateeditfreeEditdeleteshowTab.*This applies to:
ModuleUserGrouptypeId = nullPractical meaning:
Editor is the built-in broad object-maintenance role for ordinary data resourcesModel ImplementerModel Implementer inherits from User, not from Reader.
Presenter-level implicit capability:
Reader presenter shortcut to Data through the ACL hierarchyPermissions-manager implicit capabilities on schema types:
Type, can list, show, create, edit, deleteProperty, can list, show, create, edit, deletePermissions-manager implicit capabilities on schema properties:
Type and Property typesPractical meaning:
Model Implementer is the built-in schema builder roleSuperuserSuperuser is unrestricted.
It gets:
Use this role sparingly because it bypasses the normal complexity of the permission model.
The model feels cumulative because several things stack:
But the implementation is not "sum all permissions and compute a final union-minus-deny equation."
It is better described as:
That is the most accurate summary of the implementation.
Use this model carefully:
Model Implementer as a schema role, not as a business-data editor by default.Suppose a user has two roles:
In the current implementation:
That is why access design should be tested with real role combinations, not only by reading the matrix in isolation.
freeCreate and freeEdit separately because they are privileged actions.