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

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.

The reserved resource for organization templates is:

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 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:

{
  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:

{
  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
  • 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:

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 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:

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, 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 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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →