Overview
A role is a permission set: which objects an app can read or write, which fields it can see, and which platform-level capabilities it can use. Every app’s logic functions and front components inherit the permissions of the role marked withdefineApplicationRole() (see The default function role below).
src/roles/restricted-company-role.ts
SystemPermissionFlag.USER_APPS and SystemPermissionFlag.WORKSPACE_APPS
are available from twenty-sdk 2.54 onward; earlier versions name them
CONNECTED_ACCOUNTS and APPLICATIONS. The old names are deprecated aliases
for the same flags, and a role keeps each flag once. Enumerating
SystemPermissionFlag, for example with Object.keys, still lists the old
names. The API also reports these flags under their old keys, for example in
currentUserWorkspace.permissionFlags.Row-level security
Row-level permissions require the Organization plan or above — see pricing. What happens on a plan without the entitlement is covered in the notes at the end of this section. Object and field permissions decide which objects and fields a role can touch. Row-level permission predicates go further and decide which records a role can see and act on — for example, a self-service role where each external user sees only their own records. Declare predicates withrowLevelPermissionPredicates on the role. Like the rest of the manifest,
each predicate carries its own universalIdentifier, and references an object and a field by their
universalIdentifier, an operand, and (optionally) a workspaceMember field whose value is injected
at query time — so you can express “the record’s owner relation is the current workspace member”:
src/roles/partner-role.ts
Combining predicates with groups
By default a role’s predicates are combined withAND. To combine some of them with OR (or to
nest logic), declare a rowLevelPermissionPredicateGroups entry and point each predicate at it via
predicateGroupUniversalIdentifier. This role lets a partner see an Opportunity it either owns
or is the point of contact for:
src/roles/partner-opportunities-role.ts
- Give every predicate and group a stable
universalIdentifier(any uuid) — it keys the entity across upgrades, and predicates reference groups by it. - Predicates can reference objects and fields owned by your app or by Twenty’s standard objects.
- Row-level security is enforced for workspaces on the Organization plan or above. On other plans the predicates declared in an app manifest still sync, they are simply not enforced.
- The Metadata API behaves differently from the manifest here: without the entitlement,
upsertRowLevelPermissionPredicatesis rejected withROW_LEVEL_PERMISSION_FEATURE_DISABLEDrather than syncing. A workspace on a plan that does not include row-level security therefore cannot create predicates outside of an app manifest, even though therowLevelPermissionPredicatesfield is present on theRoletype when you introspect the schema.
The default function role
When you scaffold a new app, the CLI creates a default role file declared withdefineApplicationRole():
src/roles/default-role.ts
defineApplicationRole() is a thin wrapper around defineRole() that flags the role used as your application’s default at install time. Validation is identical to defineRole, but the build pipeline auto-wires its universalIdentifier into the application manifest’s defaultRoleUniversalIdentifier — so you do not need to reference it from defineApplication yourself.
Notes:
- Exactly one
defineApplicationRole(...)is allowed per app — the manifest build will fail if it finds more than one. - Use
defineRole()(notdefineApplicationRole()) for any additional roles your app ships. - Setting
defaultRoleUniversalIdentifierexplicitly ondefineApplication()is still supported for backward compatibility, but is deprecated in favor ofdefineApplicationRole(). It can also name a role your app does not define, such as the workspace custom application’s built-in default role kept bytwenty app pull; the build then cannot check that role, so it must exist in the target workspace. - A role that workspace members or API keys can be assigned (
canBeAssignedToUsersorcanBeAssignedToApiKeys, bothtruewhen omitted) is a custom role and requires the Organization plan or above: on other plans the install is refused. Keep bothfalseon roles that only your app and its agents run as.
Agent roles
The application role is what a workspace reviews when it installs your app, so it must cover everything the app itself can do. Any role you assign to an agent throughroleUniversalIdentifier on defineAgent() must not grant more than the application role: no object action, field access, permission flag or row-level scope the application role lacks. If it does, the build fails with an error listing the excess grants. Roles your app ships for workspace members or API keys are not held to this rule, since the app never runs with them. The check needs the application role in your app’s source, so it is skipped when the default role is defined elsewhere.
Best practices
- Start from the scaffolded role, then progressively restrict it — the default grants broad read access, which is rarely what you want in production.
- Replace
objectPermissionsandfieldPermissionswith the exact objects and fields your functions actually need. permissionFlagUniversalIdentifierscontrol access to platform-level capabilities. Keep them minimal.- A permission flag your app declares with
definePermissionFlag()carries apermissionType:'tool'(the default) for a capability an agent or a user can be granted,'settings'for a flag that unlocks a settings area. - See a working example:
examples/hello-world/src/roles/default-role.ts.