# How to Implement Multi-Tenancy with Logto Organizations: A Complete Guide

> Master multi-tenancy with Logto organizations. Learn to implement tenant isolation using organization helpers and access tokens for secure, scalable applications.

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

---

**Implement multi-tenancy in Logto by treating each tenant as an organization, using the tenant-organization helpers to create deterministic IDs, and requesting organization-specific access tokens with the `urn:logto:scope:organizations` scope.**

Logto provides a native multi-tenancy architecture where each SaaS tenant maps to a special organization record. This approach leverages Logto's organization-based RBAC (Role-Based Access Control) to isolate tenant data while maintaining a unified identity infrastructure. In this guide, you'll learn how to implement multi-tenancy with Logto organizations using the exact helpers and database schemas found in the logto-io/logto repository.

## Understanding Logto's Multi-Tenancy Architecture

Logto treats a **tenant** as a specialized type of **organization**. When provisioning a new SaaS tenant, you create an admin-tenant organization that stores the tenant's roles, scopes, and user memberships.

The implementation flow follows these steps:

1. Create an organization for the tenant using deterministic ID generation
2. Define tenant-specific scopes converted to OrganizationScope records
3. Create tenant-specific roles as OrganizationRole records
4. Map roles to scopes via the `tenantRoleScopes` configuration
5. Add users to the organization through membership relations
6. Grant organization-specific access tokens containing the organization ID and roles

This architecture is backed by SQL tables including `organizations`, `organization_scopes`, `organization_roles`, and `organization_user_relations` defined in `packages/schemas/tables/`.

## Core Types and Reserved Resources

Multi-tenancy in Logto relies on specific OIDC resource and scope identifiers defined in [`packages/toolkit/core-kit/src/openid.ts`](https://github.com/logto-io/logto/blob/main/packages/toolkit/core-kit/src/openid.ts).

The reserved resource for organization templates is:

```typescript
urn:logto:resource:organizations

```

Two critical scopes control organization token grants:

- **`urn:logto:scope:organizations`** – Returns the list of organization IDs a user belongs to
- **`urn:logto:scope:organization_roles`** – Returns the roles the user has within each organization

These scopes are surfaced in the `UserScope` enum (lines 33-40) in the same file, enabling your application to request organization context during authentication.

## Creating Tenant Organizations with Helper Functions

The [`packages/schemas/src/types/tenant-organization.ts`](https://github.com/logto-io/logto/blob/main/packages/schemas/src/types/tenant-organization.ts) file provides utility functions that standardize tenant-to-organization mapping. These helpers ensure consistent ID formatting and URI construction across your implementation.

**Key helper functions:**

- **`getTenantOrganizationId(tenantId)`** – Returns deterministic organization ID (`t-<tenantId>`)
- **`getTenantOrganizationCreateData(tenantId)`** – Builds the `CreateOrganization` payload with proper tenant scoping
- **`getTenantScope(scope)`** – Converts a `TenantScope` to an `OrganizationScope` record
- **`getTenantRole(role)`** – Converts a `TenantRole` to an `OrganizationRole` record
- **`tenantRoleScopes`** – Maps roles to their permitted scopes

For example, calling `getTenantOrganizationCreateData('abc123')` returns:

```typescript
{
  tenantId: 'admin',
  id: 't-abc123',
  name: 'Tenant abc123'
}

```

This deterministic approach ensures that tenant organizations are always created with predictable identifiers following the `t-<tenantId>` convention.

## Defining Tenant Scopes and Roles

Each tenant requires specific permissions (scopes) and access levels (roles) implemented through Logto's organization-level RBAC.

**Tenant scopes** represent granular permissions like `read:data` or `invite:member`. The `getTenantScope` helper converts these into `OrganizationScope` records with hyphenated IDs:

```typescript
{
  tenantId: 'admin',
  id: 'read-data',
  name: 'read:data',
  description: 'Read the tenant data.'
}

```

**Tenant roles** such as `admin` or `collaborator` become `OrganizationRole` records via `getTenantRole`. These include the `RoleType.User` designation, distinguishing them from machine-to-machine roles.

The `tenantRoleScopes` mapping links each role to its authorized scopes. For instance, the `admin` role typically maps to `[read:data, write:data, invite:member]`, while restricted roles receive narrower scope assignments.

## Database Schema for Multi-Tenancy

Logto's multi-tenancy implementation persists data across several linked tables in `packages/schemas/tables/`:

- **`organizations`** – Stores organization metadata (id, name, description, branding) per [`organizations.sql`](https://github.com/logto-io/logto/blob/main/organizations.sql)
- **`organization_user_relations`** – Links users to organizations (membership table)
- **`organization_scopes`** – Holds template scopes like `read:data` and `write:data`
- **`organization_roles`** – Stores role definitions (admin, collaborator)
- **`organization_role_scope_relations`** – Junction table connecting roles to their granted scopes
- **`organization_role_user_relations`** – Assigns specific roles to users within specific organizations

This schema implements a complete RBAC model where organization membership, role assignments, and permission scopes are strictly separated but relationally linked.

## Requesting Organization Access Tokens

To access resources within a specific tenant, clients must request an organization-specific access token. Include the organization scope and target `organization_id` in your token request:

```http
POST /oidc/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
code=AUTH_CODE&
client_id=YOUR_CLIENT_ID&
redirect_uri=https://app.example.com/callback&
scope=openid%20profile%20email%20urn:logto:scope:organizations&
organization_id=urn:logto:organization:123

```

The `organization_id` parameter must be a URN constructed using `buildOrganizationUrn` (defined in [`openid.ts`](https://github.com/logto-io/logto/blob/main/openid.ts) lines 251-265). This helper ensures consistent URN formatting: `urn:logto:organization:<id>`.

A successful response includes an `id_token` containing:

- **`organization_id`** claim – The URN of the accessed organization
- **`organization_roles`** claim – Array of role IDs the user holds within that organization

These claims enable your backend services to enforce tenant isolation and role-based permissions.

## Complete Implementation Example

The following TypeScript example demonstrates the complete multi-tenancy setup using Logto's official helpers:

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

// 1. Create the tenant organization
const tenantId = 'my-customer';
const orgCreate = getTenantOrganizationCreateData(tenantId);
// Result: { tenantId: 'admin', id: 't-my-customer', name: 'Tenant my-customer' }

await db.insert('organizations', orgCreate);

// 2. Create scopes for this organization
for (const scope of Object.values(TenantScope)) {
  const orgScope = getTenantScope(scope);
  await db.insert('organization_scopes', orgScope);
}

// 3. Create roles and bind scopes
for (const role of Object.values(TenantRole)) {
  const orgRole = getTenantRole(role);
  await db.insert('organization_roles', orgRole);

  // Link role to authorized scopes
  for (const scope of tenantRoleScopes[role]) {
    const orgScope = getTenantScope(scope);
    await db.insert('organization_role_scope_relations', {
      organizationId: orgCreate.id,
      organizationRoleId: orgRole.id,
      organizationScopeId: orgScope.id,
    });
  }
}

// 4. Add user to organization with role assignment
await db.insert('organization_user_relations', {
  tenantId: 'admin',
  organizationId: orgCreate.id,
  userId: 'user-123',
});

await db.insert('organization_role_user_relations', {
  tenantId: 'admin',
  organizationId: orgCreate.id,
  organizationRoleId: 'admin',
  userId: 'user-123',
});

```

This implementation uses the same factory functions found in [`packages/schemas/src/types/tenant-organization.ts`](https://github.com/logto-io/logto/blob/main/packages/schemas/src/types/tenant-organization.ts), guaranteeing that IDs, names, and URNs follow Logto's canonical format.

## Summary

Implementing multi-tenancy with Logto organizations requires mapping each tenant to an organization record and configuring the associated RBAC structure:

- **Treat tenants as organizations** using `getTenantOrganizationCreateData` to generate deterministic IDs in the format `t-<tenantId>`
- **Define permissions** using `getTenantScope` and `getTenantRole` to convert tenant-level scopes and roles into organization-level records
- **Configure RBAC** via the `tenantRoleScopes` mapping and the `organization_role_scope_relations` table
- **Assign membership** through `organization_user_relations` and role assignments via `organization_role_user_relations`
- **Request tokens** with `urn:logto:scope:organizations` and the `organization_id` parameter to receive claims containing `organization_id` and `organization_roles`

## Frequently Asked Questions

### How does Logto distinguish between regular organizations and tenant organizations?

Logto uses a deterministic ID convention where tenant organizations are prefixed with `t-`. The `getTenantOrganizationId` function in [`packages/schemas/src/types/tenant-organization.ts`](https://github.com/logto-io/logto/blob/main/packages/schemas/src/types/tenant-organization.ts) generates IDs like `t-abc123`, while regular organizations use standard UUIDs. This naming convention allows Logto to identify tenant-scoped resources while using the same underlying `organizations` table schema.

### What is the difference between `urn:logto:scope:organizations` and `urn:logto:scope:organization_roles`?

The `urn:logto:scope:organizations` scope returns a list of organization IDs that the authenticated user belongs to, while `urn:logto:scope:organization_roles` returns the specific roles the user holds within those organizations. Both are 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 are used together to provide complete organization context in ID tokens.

### Can I use custom scopes and roles for tenant organizations?

Yes, while Logto provides standard `TenantScope` and `TenantRole` enums, you can define custom scopes by calling `getTenantScope` with your own scope identifiers, or create organization-scoped resources directly in the `organization_scopes` table. Ensure you also update the `tenantRoleScopes` mapping to associate your custom roles with the appropriate custom scopes.

### How do I validate organization membership in my backend API?

Extract the `organization_id` claim from the access token to identify the tenant context, then verify the `organization_roles` claim to enforce RBAC. The token contains the URN format (`urn:logto:organization:<id>`) generated by `buildOrganizationUrn`, allowing your API to match the request against the stored organization records in your database.