Multi-tenant architecture
How MyDentalPractice keeps every clinic's data isolated from every other clinic's data.
Overview
MyDentalPractice is a single application that hosts many independent clinics. Each clinic — called a tenant — has its own users, patients, appointments, finances, and settings. None of that data is visible to any other clinic. Isolation is enforced at the database, the API, and the UI.
Steps to understand and verify isolation
Tenant ID is on every record. Patients, appointments, invoices, prescriptions — all have a clinic column. The database refuses queries that don't scope by tenant.
Row-level security (RLS) is the safety net. PostgreSQL RLS policies on every tenant-owned table enforce that the active connection's tenant context matches the row's clinic. If application-layer scoping ever leaks, RLS is the second line of defence.
JWTs carry tenant context. When you sign in, the JWT issued contains your clinic. Every API request carries that JWT. The backend extracts the tenant ID and applies it as the database connection's tenant context for that request.
Verify your tenant in the URL or token. The tenant ID isn't in the URL — that's a deliberate privacy decision. To see it, decode your JWT (browser dev tools → Application → Local Storage → sign-in cookie).
Patient portal is patient-tenant scoped. A patient enrolled at Clinic A cannot see anything from Clinic B. The patient portal session is tied to a single clinic. To register with multiple clinics, the patient enrols separately at each.
Platform admins are different. Platform admins are not scoped to a tenant. They have access across tenants for support and operations. Their actions are audited; tenant impersonation creates an audit log entry visible to the impersonated tenant's owner.
Confirm your data isn't leaking. From inside the app, search for a patient who exists at a sister clinic — you shouldn't find them. From the API, request the relevant operation — every record returned must have clinic matching your token. If you ever see otherwise, treat as a P0 bug and contact support immediately.