Building a Tenant Selector | Medplum

On this page

This document is a follow-up to Multi-Tenant Access Control.

In an MSO Access Control model, a single practitioner often has access to multiple tenants (for example, multiple Organization clinics) within the same Medplum Project. After login, most applications need an answer to:

This guide describes three common approaches and how to implement a tenant selector when you need one.

At a Glance

Use the decision tree to pick the option that fits your product, then jump to that section for implementation details.

Option 1: Strict Isolation Option 2: Single Account + Selector Option 3: Unified View
API-level tenant isolation Strongest (one tenant per token) Allowed tenants enforced; UI scopes within them None beyond the project
Consolidated cross-tenant views Difficult Supported (when UI omits the filter) Default
Tenant-scoped PHI screens Yes (always) Yes (when UI applies the filter) No
Login / membership model One membership per tenant per user One membership covering multiple tenants One membership covering all tenants
Tenant switching Re-auth or saved-login swap UI control (no re-auth) N/A
Implementation complexity Highest Medium Lowest

Key Concept: Allowed Tenants vs Active Tenant

When you build a "tenant selector", you're choosing and persisting the active tenant.

Option 1: Strict Isolation (Separate Accounts / Separate Projects)

Goal: Hard isolation of PHI at the API level. At any given time, the "active tenant" is the only "allowed tenant" for the user.

What This Looks Like

Each login is bound to a single tenant. Switching tenants requires switching the active login.

Switching the Active ProjectMembership

In Medplum, the "active membership" is not a client-side flag you can flip on an existing token. The OAuth login flow selects a membership, and the issued access token is bound to that membership.

Practically, "switching memberships" means one of:

How Membership Selection Works in the Login Flow

How to Configure a User with Multiple Memberships

You need to invite the user once per membership you want in the project (for example one membership per tenant in a strict-isolation MSO model). See the Invite User endpoint.

Creating the First ProjectMembership

// First membership invite for this user in this project.  
await medplum.post(`admin/projects/${projectId}/invite`, {
  resourceType: 'Practitioner',
  firstName: 'George',
  lastName: 'Washington',
  email: 'dr.gw@example.gov',
  sendEmail: false,
  membership: {
    access: [
      {
        policy: { reference: 'AccessPolicy/mso-tenant-policy' },
        parameter: [{ name: 'organization', valueReference: { reference: 'Organization/clinic-a' } }],
      },
    ],
  },
});

Creating an Additional ProjectMembership

// Second membership invite for the same user in the same project.  
await medplum.post(`admin/projects/${projectId}/invite`, {
  resourceType: 'Practitioner',
  firstName: 'George',
  lastName: 'Washington',
  email: 'dr.gw@example.gov',
  sendEmail: false,
  forceNewMembership: true,
  membership: {
    access: [
      {
        policy: { reference: 'AccessPolicy/mso-tenant-policy' },
        parameter: [{ name: 'organization', valueReference: { reference: 'Organization/clinic-b' } }],
      },
    ],
  },
});

Option 2: Single Account, Multi-Tenant Access + UI-Level Tenant Context

Goal: One login (ProjectMembership) can access one or multiple defined tenants, but the FE application chooses an "active tenant".

Granting Access to Multiple Tenants

If your AccessPolicy uses %organization:

{
  "resourceType": "ProjectMembership",
  "access": [
    {
      "policy": { "reference": "AccessPolicy/mso-policy" },
      "parameter": [
        { "name": "organization", "valueReference": { "reference": "Organization/clinic-a" } },
        { "name": "organization", "valueReference": { "reference": "Organization/clinic-b" } }
      ]
    }
  ]
}

With that membership, an access policy criteria like Patient?_compartment=%organization can allow access to patients across both tenants.

Option 3: Unified View (No Tenant Context)

Goal: Treat the project as the only boundary; the user always sees everything they're allowed to see.

When to Choose It