Roles And Permissions
What It Is
Jetstack combines several access-control layers:
- role assignment
- presenter and navigation ACL
- system permissions
- type permissions
- property permissions
- optional permission queries
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.
Why It Matters
This is one of the most important governance layers in the platform. It decides:
- who can open certain areas of the application
- who can create, show, edit, or delete objects
- who can see or modify specific properties
- whether access is global or narrowed to objects matching a permission query
To design access well, you need to understand not only what permissions exist, but how the runtime combines them.
Mental Model
Keep these layers separate:
- Base system roles provide built-in capabilities.
- Presenter ACL decides whether the user can access application areas such as presenters and tools.
- Stored permission rules add or narrow system, type, and property actions.
- Permission queries can turn an allow into an object-scoped allow.
- Surrounding availability rules for modules, views, widgets, and automations still affect what the user can actually reach.
That is why "the user has the role" is never the full answer on its own.
Roles
Roles define stable capability bundles and availability relationships. In practical terms, a role can influence:
- permission matrices
- module availability
- view availability
- widget availability
- automation availability
- which roles are available for assignment by another role
Role order also matters.
role_priority
What it is:
- the numeric priority used to order roles
How it works:
- lower numeric priority means higher importance
- user roles are sorted by priority before permission evaluation
Why it matters:
- the permission resolver iterates roles in priority order
- because the resolver stops at the first matching rule, role priority affects which rule wins first
This is a key part of the cumulative logic.
Permission Layers
Jetstack evaluates permissions in three main stored layers:
System permissions
These govern broad application capabilities such as:
- homepage access
- navigation-level capabilities
- filter-list, tag, user, version, and secret management
- AI, integration, export, and tooling capabilities
Use system permissions when the action is not tied to one business type or one property.
Type permissions
These govern actions on a type, such as:
listshowcreateeditdeletefreeCreatefreeEdit- type-specific actions
- tab actions such as
showTab.*
Use type permissions when the rule is about objects of a type as a whole.
Property permissions
These govern field-level actions such as:
- property visibility
- property create-time access
- property edit-time access
Use property permissions when access must differ at the field level.
Wildcard Rules And Specific Rules
The permissions manager supports broad wildcard-style rules through NULL values in storage:
- type permissions can be defined for all types
- actions can be defined for all actions
- property permissions can be defined for all properties of the addressed scope
- system permissions can be defined for all system actions
This is important because the runtime looks for matches in a fallback order:
- exact role + exact target + exact action
- exact role + wildcard target + exact action
- exact role + exact target + wildcard action
- exact role + wildcard target + wildcard action
That fallback order is one of the main reasons the permission model feels cumulative.
Cumulative Permission Logic As Implemented
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.
Step 1: Roles are evaluated in priority order
The current user’s roles are sorted by role_priority in ascending order before the permission manager evaluates them.
That means:
- lower priority number is checked first
- if an earlier role produces a match, later roles may never matter for that permission check
This is the first important implementation detail.
Step 2: Built-in system-role shortcuts are checked first
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:
SuperuserReaderPublishersometimes informally referred to as "Published"EditorModel Implementer
These are described in detail later in this chapter.
Step 3: The resolver looks for the first matching allow or query
If no built-in shortcut applies, the manager checks its caches in fallback order.
For type permissions, the matching order is effectively:
- role + exact type + exact action
- role + all types + exact action
- role + exact type + all actions
- role + all types + all actions
If a match is found:
truemeans allowed- a numeric query ID becomes a permission
Query falsemeans denied for that checked path
The first matching result wins.
Step 4: Explicit denies are not a universal global veto
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:
- an explicit deny on a specific role/type/action blocks that role from inheriting a broader allow through wildcard fallback
- but another role can still allow the same action later
- an earlier matching allow can effectively win before a later role is even considered
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:
- exact denies stop inherited fallback for the same role path
- later roles can still allow
- earlier higher-priority role matches win first
Step 5: Permission queries are not merged
If the match is a query-backed allow, the manager returns that Query object.
Important consequence:
- multiple matching permission queries are not combined into one composite query
- the first matching query-backed allow wins
So query-scoped permissions are resolved as a single winning rule, not as an intersection or union of all matching permission queries.
Step 6: If no match exists, access fails
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.
Privileged Actions: freeCreate And freeEdit
The 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:
- privileged actions need a more explicit rule
- they should not be assumed to come from broad "all actions" permissions
Superuseris still unrestricted
This is an important guardrail in the implementation.
Property Permission Logic
Property permissions use the same general pattern as type permissions:
- roles are checked in order
- exact match is preferred
- broader fallback is used if not blocked by an exact deny
- query-backed allows return a
Query
Property permissions have two extra implementation details:
duplicated_from_id special-case
The 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.
Model schema property shortcut
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 Permission Logic
System permissions are simpler.
The manager checks:
- exact role + exact system action
- exact role + wildcard system action
As with other layers:
- the first matching allow or query wins
- explicit denies mainly stop fallback for the same exact role/action path
Permission Queries
Permission queries are what turn "allowed" into "allowed only for matching objects."
If a permission rule contains a query:
- the permission manager returns the query object instead of plain
true - the caller can then apply that query to the working object selection
How query application works
When a permission query is applied to a database selection, the manager:
- resets the query parameters
- applies the query parameters
- reads the query conditions
- pushes the resulting
whereandwhereOrconditions into the target selection
The 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.
Presenter ACL And System Roles
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.
User
User is the base authenticated system role.
It gets baseline access to application infrastructure such as:
HomepageModuleSearchActiveResourceUpload- user and object action endpoints
- object and user canvas/checklist interaction endpoints
This is the foundation that the more specific system roles build on.
Reader
Reader inherits from User.
Presenter-level implicit capability:
- gains access to the
Dataarea
Permissions-manager implicit capabilities:
- can
list - can
show - can access
showTab.*
This applies to:
ModuleUserGroup- custom types
- type-neutral calls represented by
typeId = nullin the evaluator
Practical meaning:
Readeris the built-in role for read-oriented access to ordinary data areas
Publisher
The 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:
- inherits
Readerpresenter access, includingData
Permissions-manager implicit capabilities:
- can
list - can
show - can
create - can access
showTab.*
This applies to:
ModuleUserGroup- custom types
- evaluator calls with
typeId = null
Practical meaning:
Publisheris a lightweight creator role- it can create but does not get the full broad edit/delete shortcut of
Editor
Editor
Editor inherits from Reader.
Presenter-level implicit capability:
- inherits
Readerpresenter access, includingData
Permissions-manager implicit capabilities:
- can
list - can
show - can
create - can
freeCreate - can
edit - can
freeEdit - can
delete - can access
showTab.*
This applies to:
ModuleUserGroup- custom types
- evaluator calls with
typeId = null
Practical meaning:
Editoris the built-in broad object-maintenance role for ordinary data resources
Model Implementer
Model Implementer inherits from User, not from Reader.
Presenter-level implicit capability:
- it does not inherit the
Readerpresenter shortcut toDatathrough the ACL hierarchy - its special power comes mainly from schema-level implicit permissions in the permissions manager
Permissions-manager implicit capabilities on schema types:
- on
Type, canlist,show,create,edit,delete - on
Property, canlist,show,create,edit,delete
Permissions-manager implicit capabilities on schema properties:
- can access schema properties belonging to the
TypeandPropertytypes
Practical meaning:
Model Implementeris the built-in schema builder role- it is meant for designing types and properties rather than acting as a general data editor
Superuser
Superuser is unrestricted.
It gets:
- full presenter/resource ACL access
- full implicit allow in the permissions manager
Use this role sparingly because it bypasses the normal complexity of the permission model.
Why The Logic Feels Cumulative
The model feels cumulative because several things stack:
- users can have several roles
- each role can have exact and wildcard rules
- system roles add built-in capabilities
- stored rules can add further access
- permission queries can narrow allowed object scope
But the implementation is not "sum all permissions and compute a final union-minus-deny equation."
It is better described as:
- ordered role evaluation
- built-in shortcuts first
- first matching rule wins
- exact denies block fallback for the same role path
- later roles can still allow
That is the most accurate summary of the implementation.
Design Guidance
Use this model carefully:
- Use role priority intentionally, because it affects first-match evaluation.
- Use built-in system roles for broad baseline capability, not for every special case.
- Use explicit stored permissions when built-in shortcuts are too broad.
- Use permission queries when scope depends on object data.
- Do not assume a deny on one role automatically cancels an allow from another role.
- Treat
Model Implementeras a schema role, not as a business-data editor by default.
Example
Suppose a user has two roles:
- a high-priority restricted role with an exact deny on one type/action
- a lower-priority broader role with an allow on the same type/action
In the current implementation:
- the exact deny prevents wildcard fallback for the restricted role
- but the broader lower-priority role may still allow the action if no earlier positive match already resolved the result
That is why access design should be tested with real role combinations, not only by reading the matrix in isolation.
Best Practices
- Keep the number of roles understandable.
- Document the meaning of role priority, not just the role name.
- Use permission queries to express scope, not to hide core business logic.
- Prefer explicit permission design over relying blindly on system-role shortcuts.
- Be careful when combining Reader, Publisher, Editor, and custom roles on one user.
- Review
freeCreateandfreeEditseparately because they are privileged actions.