# Security Measures for Permissions in Twenty CRM: Role-Based Access Control Implementation

> Explore Twenty CRM's robust security for permissions. Learn about role-based access control, granular flags, and row-level predicates for secure data handling.

- Repository: [Twenty/twenty](https://github.com/twentyhq/twenty)
- Tags: deep-dive
- Published: 2026-03-27

---

**Twenty CRM implements a defense-in-depth permission system using role-based access control (RBAC), granular permission flags, and row-level predicates, with centralized enforcement through NestJS guards and the `PermissionsService`.**

Twenty CRM, an open-source Salesforce alternative, employs a sophisticated security architecture to regulate access across users, API keys, and internal applications. The permission model combines role configurations with fine-grained feature toggles and object-level permissions, ensuring comprehensive data protection while maintaining flexibility for complex organizational structures.

## Authentication Context and Role Resolution

The foundation of Twenty's security measures lies in determining the caller's identity and translating it into a structured permission configuration. The `resolveRolePermissionConfig` function in [`packages/twenty-server/src/engine/twenty-orm/utils/resolve-role-permission-config.util.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/twenty-orm/utils/resolve-role-permission-config.util.ts) handles this resolution by inspecting the authentication context and returning a `RolePermissionConfig`.

This configuration supports three distinct modes:

- **System bypass** (`shouldBypassPermissionChecks: true`) for internal system operations
- **Intersection of roles** requiring the actor to satisfy permissions from multiple roles simultaneously
- **Union of roles** allowing permission aggregation across multiple role assignments

```typescript
// resolveRolePermissionConfig implementation
if (isSystemAuthContext(authContext)) {
  return { shouldBypassPermissionChecks: true };
}
if (isApiKeyAuthContext(authContext)) {
  const roleId = apiKeyRoleMap[authContext.apiKey.id];
  return roleId ? { intersectionOf: [roleId] } : null;
}
if (isApplicationAuthContext(authContext) && isDefined(authContext.application.defaultRoleId)) {
  return { intersectionOf: [authContext.application.defaultRoleId] };
}
if (isUserAuthContext(authContext)) {
  const roleId = userWorkspaceRoleMap[authContext.userWorkspaceId];
  return roleId ? { intersectionOf: [roleId] } : null;
}

```

The `RolePermissionConfig` type definition in [`packages/twenty-server/src/engine/twenty-orm/types/role-permission-config.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/twenty-orm/types/role-permission-config.ts) formalizes these structures, enabling the permission system to handle complex scenarios like users with multiple roles or service accounts with elevated privileges.

## Permission Flags and Feature Access

Twenty CRM utilizes fine-grained **permission flags** stored in [`packages/twenty-sdk/src/sdk/roles/permission-flag-type.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-sdk/src/sdk/roles/permission-flag-type.ts) to control feature access. These boolean toggles—such as `VIEWS`, `API_KEYS_AND_WEBHOOKS`, and `IMPERSONATE`—exist at both the role level (`permissionFlags`) and per-object level (`objectsPermissions`).

The `PermissionsService` in [`packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts) evaluates these flags through methods like `hasViewsPermission`, which checks permissions differently based on the actor type:

```typescript
// PermissionsService.hasViewsPermission implementation
if (isDefined(userWorkspaceId)) {
  const permissions = await this.permissionsService.getUserWorkspacePermissions({
    userWorkspaceId,
    workspaceId,
  });
  return permissions.permissionFlags[PermissionFlagType.VIEWS] ?? false;
}
if (isDefined(apiKeyId)) {
  return this.permissionsService.userHasWorkspaceSettingPermission({
    workspaceId,
    apiKeyId,
    setting: PermissionFlagType.VIEWS,
  });
}
return false;

```

This dual-path evaluation ensures that API keys undergo the same rigorous permission checks as human users, preventing privilege escalation through alternative authentication methods.

## Object-Level and Row-Level Security

Beyond feature flags, Twenty implements **object-level permissions** and **row-level predicates** to restrict data visibility. The `computePermissionIntersection` utility in [`packages/twenty-server/src/engine/twenty-orm/utils/compute-permission-intersection.util.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/twenty-orm/utils/compute-permission-intersection.util.ts) merges permissions from multiple roles into a single effective permission set.

This utility handles:

- `canRead`, `canUpdate`, `canSoftDelete`, and `canDestroy` operations
- Field-level restrictions within objects
- Intersection logic for multi-role scenarios

When a user belongs to multiple roles, the system computes the restrictive intersection of permissions, ensuring that a user only accesses data allowed by *all* their assigned roles, not just one.

## Enforcing Access with NestJS Guards

Twenty CRM integrates permission checks into the HTTP and GraphQL request pipeline using NestJS guards. The `UpdateViewPermissionGuard` in [`packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/update-view-permission.guard.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/metadata-modules/view-permissions/guards/update-view-permission.guard.ts) exemplifies this pattern:

```typescript
// UpdateViewPermissionGuard.canActivate implementation
const gqlContext = GqlExecutionContext.create(context);
const request = gqlContext.getContext().req;
const args = gqlContext.getArgs();
const viewId = typeof args?.id === 'string' ? args.id : request.params?.id;

return this.viewAccessService.canUserModifyView(
  viewId,
  request.userWorkspaceId,
  request.workspace.id,
  request.apiKey?.id,
);

```

Guards extract request context, delegate validation to specialized services, and reject unauthorized requests before they reach business logic controllers.

## Special Permission Rules for View Ownership

The security measures include contextual exceptions for specific resource types. The `ViewAccessService` in [`packages/twenty-server/src/engine/metadata-modules/view-permissions/services/view-access.service.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/metadata-modules/view-permissions/services/view-access.service.ts) implements a special rule allowing view owners to modify their own **UNLISTED** views even without the global `VIEWS` permission flag:

```typescript
const isOwnUnlistedView =
  view.visibility === ViewVisibility.UNLISTED &&
  view.createdByUserWorkspaceId === userWorkspaceId;

if (isOwnUnlistedView) {
  return true;
}

```

This pattern demonstrates Twenty's ability to combine role-based permissions with resource ownership checks for nuanced access control.

## Practical Implementation Examples

### Checking View Creation Permissions

```typescript
async function canCreateView(
  visibility: ViewVisibility,
  ctx: RequestContext,
): Promise<boolean> {
  const viewAccess = new ViewAccessService(viewService, permissionsService);
  return viewAccess.canUserCreateView(
    visibility,
    ctx.userWorkspaceId,
    ctx.workspaceId,
    ctx.apiKey?.id,
  );
}

```

### Retrieving Effective Object Permissions

```typescript
import { PermissionsService } from '@/engine/metadata-modules/permissions/permissions.service';

async function getEffectiveObjectPermissions(
  userWorkspaceId: string,
  workspaceId: string,
) {
  const { objectsPermissions } = await permissionsService.getUserWorkspacePermissions({
    userWorkspaceId,
    workspaceId,
  });
  
  // Returns a map of objectMetadataId → permission flags
  return objectsPermissions;
}

```

### Configuring Tool Permissions

```typescript
import { RolePermissionConfig } from '@/engine/twenty-orm/types/role-permission-config';

// Require user to belong to BOTH role A AND role B
const config: RolePermissionConfig = {
  intersectionOf: ['role-a-id', 'role-b-id'],
};

const hasToolPermission = await permissionsService.hasToolPermission(
  config,
  workspaceId,
  PermissionFlagType.IMPORT_CSV,
);

```

## Summary

- **Role-based architecture**: Twenty CRM uses `RolePermissionConfig` to manage permissions through intersections, unions, or bypasses based on authentication context.
- **Granular flag system**: `PermissionFlagType` provides feature-level toggles enforced by the centralized `PermissionsService`.
- **Layered security**: The system combines authentication context resolution, permission flags, object-level permissions, and row-level predicates for defense-in-depth.
- **Guard integration**: NestJS guards like `UpdateViewPermissionGuard` enforce checks automatically at the request boundary.
- **Contextual exceptions**: Special rules, such as unlisted view ownership, allow fine-tuned access without compromising overall security posture.

## Frequently Asked Questions

### How does Twenty CRM handle permissions for API keys versus regular users?

According to the source code in [`resolve-role-permission-config.util.ts`](https://github.com/twentyhq/twenty/blob/main/resolve-role-permission-config.util.ts), API keys resolve to a single role through the `apiKeyRoleMap` lookup, returning an `intersectionOf` configuration containing that role ID. The `PermissionsService` then evaluates API key permissions through dedicated methods like `userHasWorkspaceSettingPermission`, ensuring API keys undergo the same flag checks as users but without user workspace associations.

### What happens when a user has multiple roles assigned?

The `computePermissionIntersection` utility in [`compute-permission-intersection.util.ts`](https://github.com/twentyhq/twenty/blob/main/compute-permission-intersection.util.ts) merges permissions from multiple roles by computing the intersection of capabilities. This means a user gains only the permissions shared across all their assigned roles, following the principle of least privilege. For example, if Role A allows reading Contacts but Role B does not, the user cannot read Contacts.

### Can system operations bypass all permission checks?

Yes, system contexts identified by `isSystemAuthContext` in the role resolution utility receive a `RolePermissionConfig` with `shouldBypassPermissionChecks: true`. This allows internal system processes to perform necessary operations without restrictive permission overhead, though this bypass applies exclusively to system-level authentication contexts and never to user or API key requests.

### How are row-level security predicates implemented in Twenty?

Row-level restrictions are implemented through SQL-level filters combined with object permissions via `computePermissionIntersection`. These predicates restrict which records a user can access based on custom conditions beyond role assignments. The system merges these row-level rules with the `canRead`, `canUpdate`, and other object-level permission flags to determine final access rights at the database query level.