# How Twenty CRM Implements Multi-Tenancy for Workspaces: Schema-Per-Tenant Architecture

> Discover how Twenty CRM implements multi-tenancy for workspaces using a schema-per-tenant architecture. Learn how each workspace gets a dedicated PostgreSQL schema for secure data isolation.

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

---

**Twenty CRM achieves multi-tenancy for workspaces by isolating each tenant in a dedicated PostgreSQL schema, using a centralized ORM manager to route all queries to the correct tenant context while maintaining global workspace metadata in a shared `core` schema.**

Twenty CRM serves multiple organizations—referred to as **workspaces**—from a single application instance. To guarantee strict data isolation without the operational overhead of separate database instances, the platform implements a true multi-tenant architecture where every workspace receives its own isolated PostgreSQL schema. This design ensures that tenant data never commingles while allowing the application to scale efficiently on shared infrastructure.

## How Twenty CRM Isolates Tenant Data with PostgreSQL Schemas

Instead of co-mingling all customer data in shared tables or maintaining separate database connections per tenant, Twenty creates a distinct PostgreSQL schema for each workspace. This approach provides strong data isolation at the database level while keeping the connection pool and application code unified.

### Workspace Schema Naming Convention

The system generates a deterministic schema name for every workspace using the `getWorkspaceSchemaName` utility. Located in [`packages/twenty-server/src/engine/workspace-datasource/utils/get-workspace-schema-name.util.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/workspace-datasource/utils/get-workspace-schema-name.util.ts), this function converts the workspace UUID into a base36-encoded string prefixed with `workspace_`.

```ts
import { getWorkspaceSchemaName } from '@/workspace/utils/get-workspace-schema-name.util';

const schema = getWorkspaceSchemaName('8f6d5a7c-a3b2-4c9e-b1f0-d2e3f4a5b6c7');
// → "workspace_1wgvd1injqtife6y4rvfbu3h5"

```

This deterministic naming allows the system to resolve the correct schema for any workspace ID without additional database lookups.

### The Global Workspace Registry

While tenant data lives in isolated schemas, global workspace metadata resides in the shared `core` schema. The `WorkspaceEntity` defined in [`packages/twenty-server/src/engine/core-modules/workspace/workspace.entity.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/workspace/workspace.entity.ts) stores activation status, subdomain configuration, and display names in the `core.workspace` table. This registry enables the application to validate workspace existence and manage cross-cutting concerns before routing to tenant-specific schemas.

## Core Components Managing Multi-Tenancy for Workspaces

Twenty's multi-tenancy implementation relies on three interconnected services that handle context propagation, data access, and performance optimization across tenant boundaries.

### GlobalWorkspaceOrmManager: The Tenant Gateway

The `GlobalWorkspaceOrmManager` class in [`packages/twenty-server/src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager.ts) serves as the primary abstraction for tenant-aware data operations. It provides two critical methods for interacting with workspace data:

- **`getRepository(workspaceId, entity)`**: Returns a TypeORM repository automatically bound to the workspace's specific schema.
- **`executeInWorkspaceContext(callback, authContext)`**: Executes arbitrary logic within a fully loaded workspace context, including metadata and permissions.

This manager ensures that all database queries target the correct schema without requiring developers to manually manage schema routing.

### WorkspaceAuthContext: Injecting Tenant Identity

To maintain security boundaries, the system stores the current user's workspace affiliation in `WorkspaceAuthContext`. Defined in [`packages/twenty-server/src/engine/core-modules/auth/types/workspace-auth-context.type.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/auth/types/workspace-auth-context.type.ts), this object propagates the workspace ID through the request lifecycle. The ORM manager consumes this context to resolve the appropriate schema, ensuring that data access is automatically scoped to the authenticated tenant.

### WorkspaceCacheService: Optimizing Per-Tenant Performance

Loading workspace metadata for every request would create significant database overhead. The `WorkspaceCacheService` in [`packages/twenty-server/src/engine/workspace-cache/services/workspace-cache.service.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/workspace-cache/services/workspace-cache.service.ts) caches object maps, field definitions, and permission sets per workspace. When `GlobalWorkspaceOrmManager` initializes a workspace context, it retrieves cached metadata rather than querying the `core` schema repeatedly, maintaining low latency for multi-tenant operations.

## Provisioning New Workspace Tenants

Creating a new workspace involves both metadata registration and physical schema initialization. The process follows a strict sequence to ensure tenant isolation from inception.

When a user creates a workspace, the system first inserts a record into the `core.workspace` table via `WorkspaceEntity`. It then invokes `getWorkspaceSchemaName` to compute the tenant's schema identifier. Finally, the workspace export service located at [`packages/twenty-server/src/database/commands/workspace-export/workspace-export.service.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/database/commands/workspace-export/workspace-export.service.ts) executes `CREATE SCHEMA IF NOT EXISTS` followed by metadata-driven migrations that instantiate the required tables within the new tenant schema.

## Working with Workspace Data: Practical Code Examples

The following patterns demonstrate how application code interacts with the multi-tenant architecture using Twenty's ORM abstractions.

### Computing Schema Names for Raw SQL

When executing low-level SQL operations outside TypeORM repositories, use the schema utility to resolve the correct identifier:

```ts
import { getWorkspaceSchemaName } from '@/workspace/utils/get-workspace-schema-name.util';

const schema = getWorkspaceSchemaName('8f6d5a7c-a3b2-4c9e-b1f0-d2e3f4a5b6c7');
// → "workspace_1wgvd1injqtife6y4rvfbu3h5"

```

### Accessing Repositories by Workspace ID

Services inject `GlobalWorkspaceOrmManager` to obtain repositories scoped to specific tenants:

```ts
@Injectable()
export class PersonService {
  constructor(
    private readonly globalWorkspaceOrm: GlobalWorkspaceOrmManager,
  ) {}

  async findAll(workspaceId: string): Promise<Person[]> {
    const repo = await this.globalWorkspaceOrm.getRepository(
      workspaceId,
      'person',
    );
    return repo.find();
  }
}

```

### Executing Logic Within a Workspace Context

For operations requiring full workspace metadata and permissions, wrap execution inside the context manager:

```ts
await this.globalWorkspaceOrm.executeInWorkspaceContext(
  async () => {
    // All repository calls here automatically target the workspace schema
    // Auth context contains workspace ID and role information
    const notes = await noteRepo.find({ where: { title: Like('%meeting%') } });
    return notes;
  },
  { workspace: { id: workspaceId, user: { id: systemUserId } } },
);

```

## Summary

Twenty CRM implements multi-tenancy for workspaces through a robust schema-per-tenant architecture that balances isolation with performance. Key implementation details include:

- Each workspace receives a dedicated PostgreSQL schema named via `getWorkspaceSchemaName` to ensure deterministic routing.
- Global workspace metadata resides in the shared `core.workspace` table managed by `WorkspaceEntity`.
- `GlobalWorkspaceOrmManager` serves as the central gateway for schema-bound repositories and tenant context execution.
- `WorkspaceAuthContext` propagates tenant identity from the authentication layer to the data layer.
- `WorkspaceCacheService` eliminates redundant metadata queries by caching workspace configurations per tenant.

## Frequently Asked Questions

### How does Twenty CRM ensure data isolation between workspaces?

Twenty enforces isolation at the database level by storing each workspace's data in a separate PostgreSQL schema. The `GlobalWorkspaceOrmManager` guarantees that every query executes against the correct schema by resolving the workspace ID from `WorkspaceAuthContext`. This schema-per-tenant approach prevents cross-contamination of data between organizations while allowing shared application infrastructure.

### What performance optimizations exist for multi-tenant queries?

The platform uses `WorkspaceCacheService` to cache metadata, permission maps, and feature flags for each workspace in memory. When application code enters a workspace context via `executeInWorkspaceContext`, the system retrieves cached configuration rather than querying the `core` schema repeatedly. This caching layer minimizes database round trips and maintains consistent latency regardless of the number of active workspaces.

### Can Twenty CRM support single-tenant deployment without schema separation?

While the codebase is architected for schema-based multi-tenancy, the underlying TypeORM configuration could theoretically target a single schema. However, the `getWorkspaceSchemaName` utility and `GlobalWorkspaceOrmManager` are designed specifically for schema routing. Removing multi-tenancy would require significant modifications to the workspace provisioning logic in [`workspace-export.service.ts`](https://github.com/twentyhq/twenty/blob/main/workspace-export.service.ts) and the ORM manager's schema resolution mechanisms.

### How are background jobs handled across multiple workspace tenants?

Background workers utilize the same `GlobalWorkspaceOrmManager` abstraction as HTTP requests. When processing jobs, the worker explicitly passes the target workspace ID to `executeInWorkspaceContext` or `getRepository`, ensuring the job operates within the correct tenant schema. The workspace cache allows workers to rapidly switch contexts between jobs targeting different organizations without reinitializing database connections.