# How Logto Implements Multi-Tenancy with Organizations

> Discover how Logto implements multi-tenancy with organizations by modeling tenants as special organizations. Learn about their unique naming convention for seamless integration.

- Repository: [Logto/logto](https://github.com/logto-io/logto)
- Tags: how-to-guide
- Published: 2026-07-05

---

**Logto implements multi-tenancy by modeling each tenant as a special organization within the admin tenant, using the `t-${tenantId}` naming convention to reuse existing role-based access control and organization token mechanisms.**

Logto is an open-source identity and access management (IAM) platform that handles complex multi-tenant scenarios through a unified organization model. Rather than building separate tenant-specific infrastructure, the codebase leverages existing organization, role, and scope mechanisms to isolate data and manage per-tenant permissions. This architecture, implemented in the `logto-io/logto` repository, allows the platform to scale horizontally while maintaining strict data isolation through the `organizations` table in the admin tenant.

## Core Concepts of Logto Multi-Tenancy

### Tenant Organizations

In Logto, a *tenant* is represented by a **tenant organization**—a special entity living in the admin tenant. When a new tenant is created, Logto generates a unique organization ID by prefixing the tenant ID with `t-` (e.g., `t-my-tenant`). This convention is defined in [`packages/schemas/src/types/tenant-organization.ts`](https://github.com/logto-io/logto/blob/main/packages/schemas/src/types/tenant-organization.ts), which exports the `getTenantOrganizationId(tenantId)` helper and the frozen `CreateOrganization` object used during provisioning.

### Tenant Scopes and Roles

Access control within tenant organizations relies on two enumerations: `TenantScope` and `TenantRole`. The `TenantScope` enum defines granular actions such as reading tenant data, writing configurations, or managing members. The `TenantRole` enum maps these scopes to human-readable roles: **Admin** receives all available scopes, while **Collaborator** receives a limited subset. This mapping is stored in the `tenantRoleScopes` relationship, allowing the platform to enforce permissions uniformly across all tenant organizations.

### Organization Tokens (RFC 0001)

To authorize requests across tenant boundaries, Logto issues **organization tokens**—JWTs that carry the organization context via the claim `urn:logto:organization:${orgId}`. These tokens are requested using the `urn:logto:scope:organizations` scope and the `urn:logto:resource:organizations` resource indicator. The token structure is defined in [`packages/toolkit/core-kit/src/openid.ts`](https://github.com/logto-io/logto/blob/main/packages/toolkit/core-kit/src/openid.ts) and is consumed by the Management API, Cloud APIs, and token-exchange flows to enforce per-organization access policies.

## How the Multi-Tenancy Flow Works

### Tenant Creation and Seeding

When a tenant is provisioned—either via CLI seed commands or programmatically—Logto calls `getTenantOrganizationCreateData(tenantId)` to generate the required insertion data. This creates a record in the admin tenant's `organizations` table with the ID `t-${tenantId}`.

```typescript
// packages/cli/src/commands/database/seed/tenant-organizations.ts
import { getTenantOrganizationCreateData } from '@logto/schemas';

const createData = getTenantOrganizationCreateData(tenantId);
await queries.organizations.insert(createData);

```

### First Admin User Provisioning

During the registration of the first admin user, the [`submit-interaction.ts`](https://github.com/logto-io/logto/blob/main/submit-interaction.ts) handler detects the `isCreatingFirstAdminUser` flag and automatically creates the tenant organization if it does not exist. It then inserts the user into the `organizations.relations.users` table and assigns the `TenantRole.Admin` role via `organizations.relations.usersRoles`.

```typescript
// packages/core/src/routes/interaction/actions/submit-interaction.ts
const organizationId = getTenantOrganizationId(defaultTenantId);
await organizations.relations.users.insert({ organizationId, userId: id });
await organizations.relations.usersRoles.insert({
  organizationId,
  organizationRoleId: getTenantRole(TenantRole.Admin).id,
  userId: id,
});

```

### Token Issuance via OIDC Grants

When a client requests an access token with the organizations scope, the OIDC grant handlers—including [`client-credentials.ts`](https://github.com/logto-io/logto/blob/main/client-credentials.ts), [`refresh-token.ts`](https://github.com/logto-io/logto/blob/main/refresh-token.ts), and `token-exchange`—attach the tenant organization URN to the JWT payload. This allows downstream services to identify the target tenant without additional database lookups.

```typescript
// packages/core/src/oidc/grants/client-credentials.ts
if (scopes.includes(UserScope.Organizations)) {
  const organizationUrn = buildOrganizationUrn(getTenantOrganizationId(tenantId));
  // Attach to token payload as context.organization
}

```

### Runtime Membership Verification

Every protected endpoint extracts the `organization` claim from the incoming token and verifies membership against the `organizations.relations.users` table. If the user is not associated with the specified tenant organization, the API returns a `403 Forbidden` response. This check ensures that users can only access data belonging to tenants where they hold active membership and appropriate roles.

## Implementation Examples

### Creating a Tenant Organization Programmatically

To create a tenant organization via code, use the schema helpers to generate the correct ID and creation data, then insert into the admin tenant's organization table.

```typescript
import {
  getTenantOrganizationCreateData,
  getTenantOrganizationId,
  getTenantRole,
  TenantRole,
} from '@logto/schemas';

// Generate creation payload for tenant 'acme-corp'
const tenantId = 'acme-corp';
const createData = getTenantOrganizationCreateData(tenantId);

// Insert organization (requires admin tenant database context)
await queries.organizations.insert(createData);

// Assign default admin role
await queries.organizations.relations.roles.insert({
  organizationId: getTenantOrganizationId(tenantId),
  roleId: getTenantRole(TenantRole.Admin).id,
});

```

### Adding Users to a Tenant Organization

New users can be associated with a tenant organization using the relations API.

```typescript
import { getTenantOrganizationId } from '@logto/schemas';

const organizationId = getTenantOrganizationId(tenantId);
await queries.organizations.relations.users.insert({
  organizationId,
  userId: newUserId,
});

```

### Issuing Organization Tokens

Clients obtain organization tokens through the client-credentials flow by specifying the organizations resource and scope.

```typescript
import {
  buildOrganizationUrn,
  getTenantOrganizationId,
} from '@logto/schemas';

// Request: grant_type=client_credentials&scope=urn:logto:scope:organizations
//          &resource=urn:logto:resource:organizations

const orgUrn = buildOrganizationUrn(getTenantOrganizationId(tenantId));
const token = await issueJwt({
  sub: clientId,
  scope: 'urn:logto:scope:organizations',
  context: { organization: orgUrn },
});

```

### Verifying Membership in API Endpoints

API routes must validate that the requesting user belongs to the organization specified in the token.

```typescript
import { getOrganizationIdFromUrn } from '@logto/schemas';

async function handler(ctx) {
  const token = await verifyJwt(ctx.headers.authorization);
  const orgUrn = token.context?.organization;
  
  if (!orgUrn) {
    ctx.throw(401, 'Missing organization token');
  }
  
  const orgId = getOrganizationIdFromUrn(orgUrn);
  const isMember = await queries.organizations.relations.users.findOne({
    organizationId: orgId,
    userId: token.sub,
  });
  
  if (!isMember) {
    ctx.throw(403, 'User not a member of the organization');
  }
  
  // Proceed with tenant-specific business logic
}

```

## Summary

- **Tenant organizations** use the `t-${tenantId}` naming convention to uniquely identify tenants within the admin tenant's organization table, defined in [`packages/schemas/src/types/tenant-organization.ts`](https://github.com/logto-io/logto/blob/main/packages/schemas/src/types/tenant-organization.ts).
- **Role-based access control** is implemented through `TenantScope` and `TenantRole` enums, allowing granular permissions for Admin and Collaborator roles without separate tenant tables.
- **Organization tokens** carry the tenant context as a URN (`urn:logto:organization:${orgId}`) and are issued via standard OIDC grants in `packages/core/src/oidc/grants/`.
- **Automatic provisioning** creates tenant organizations during CLI seeding or first admin registration, handled in [`packages/core/src/routes/interaction/actions/submit-interaction.ts`](https://github.com/logto-io/logto/blob/main/packages/core/src/routes/interaction/actions/submit-interaction.ts).
- **Runtime isolation** is enforced by verifying user membership against `organizations.relations.users` before serving protected resources.

## Frequently Asked Questions

### How does Logto isolate data between tenants?

Logto isolates tenant data by treating each tenant as a distinct organization entity within the admin tenant. Every data access request is scoped to an organization ID formatted as `t-${tenantId}`, and the platform verifies user membership in the `organizations.relations.users` table before allowing access. This ensures that users can only interact with resources belonging to organizations where they hold valid roles, effectively creating hard boundaries between tenant datasets.

### What is the difference between a standard organization and a tenant organization?

A **standard organization** represents a business entity or team within a tenant (e.g., a company using your SaaS), while a **tenant organization** represents the tenant itself within the admin context. Tenant organizations are identified by the `t-` prefix (e.g., `t-default` for the default tenant) and are created automatically during tenant provisioning. They utilize the same underlying database schema and relations, but serve as the root container for tenant-wide configuration and administrative access.

### How are permissions structured within tenant organizations?

Permissions are structured using the `TenantScope` enum (defining actions like `read:data`, `write:data`, `manage:members`) and the `TenantRole` enum (defining `Admin` and `Collaborator` roles). Each role maps to a specific subset of scopes in the `tenantRoleScopes` table. When a user is assigned to a tenant organization, they receive a role that determines their permissible actions, and these claims are embedded in the organization token issued by the OIDC grant handlers.

### When is a tenant organization created during user registration?

A tenant organization is created during the registration flow when the `isCreatingFirstAdminUser` flag is true, indicating this is the initial setup of the Logto instance. The [`submit-interaction.ts`](https://github.com/logto-io/logto/blob/main/submit-interaction.ts) handler invokes `getTenantOrganizationCreateData` to generate the organization record and immediately assigns the new user to the `TenantRole.Admin` role. For subsequent users, JIT (Just-In-Time) provisioning via the `provisionOrganizations` library may create tenant organizations based on email domain policies or manual invitation flows.