Skip to main content

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 with defineApplicationRole() (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 with rowLevelPermissionPredicates 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
Because predicates ship in the manifest, they are created, updated, and removed together with the role on every install and upgrade — there is no separate post-install step to keep in sync.

Combining predicates with groups

By default a role’s predicates are combined with AND. 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
Notes:
  • 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, upsertRowLevelPermissionPredicates is rejected with ROW_LEVEL_PERMISSION_FEATURE_DISABLED rather 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 the rowLevelPermissionPredicates field is present on the Role type when you introspect the schema.

The default function role

When you scaffold a new app, the CLI creates a default role file declared with defineApplicationRole():
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() (not defineApplicationRole()) for any additional roles your app ships.
  • Setting defaultRoleUniversalIdentifier explicitly on defineApplication() is still supported for backward compatibility, but is deprecated in favor of defineApplicationRole(). It can also name a role your app does not define, such as the workspace custom application’s built-in default role kept by twenty 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 (canBeAssignedToUsers or canBeAssignedToApiKeys, both true when omitted) is a custom role and requires the Organization plan or above: on other plans the install is refused. Keep both false on 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 through roleUniversalIdentifier 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 objectPermissions and fieldPermissions with the exact objects and fields your functions actually need.
  • permissionFlagUniversalIdentifiers control access to platform-level capabilities. Keep them minimal.
  • A permission flag your app declares with definePermissionFlag() carries a permissionType: '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.