Multi-tenancy question, after a recommendation.
# support
k
Goal. Many independent clinics on one Medplum project. Each clinic sees only its own patients and notes. But a person treated at three clinics should see all three in one place in the patient portal. So: walled off between clinics, joined up for the patient. A clinic must never learn that another clinic is involved. Method 1 — one Patient shared by all clinics, tagged with each in meta.accounts. Trouble: a note saved at clinic A inherits the patient's whole tag list, so clinic B reads it too. Removing clinic B from the note as an admin works, but the doctor's next ordinary save re-copies the tags from the patient and clinic B comes back, silently. A bot re-tagging after each write cleans up after the exposure, not before it. Method 2 — one Patient per clinic, each tagged with only that clinic, tied together by a Person linking them. Notes inherit a single clinic tag, so clinical isolation works exactly as we want. Trouble: the Person is the only record that knows the copies are the same human, and has to be tagged with every clinic that person attends. So it effectively says "this human attends A, B, C and D", readable by all of them. A doctor at clinic A can search Person filtered by clinic B and learn which of their own patients also go there. Stripping name and DOB off the Person removes the detail but not the disclosure. Questions 1. Which would you recommend for patient-centric records across tenants? 2. Any supported way to share one Patient (method 1) while a note stays visible only to the clinic that wrote it? 3. Any way to link records across clinics (method 2) without the linking record naming the clinics to each other? 4. At 100 clinics: method 1 gives one Patient with 100 tags and every note inheriting all 100; method 2 gives 100 records, a Person with 100 tags, and 100 access entries on the membership. Does either degrade? Thank you
a
Hey @keshav-borderless! Good question—this is still something that will take advantage of our tenanting model using meta.accounts, but since you want no visibility into each other's clinical data while maintaining full visibility for patients, it gets a bit more complicated. We'd recommend using meta.tag with criteria-based AccessPolicies to control clinic access to individual resources, since those tags don't automatically propagate from the Patient like meta.accounts does. For example, clinic A's notes could carry a clinic-a tag, with its AccessPolicy permitting only notes matching that tag; clinic B's notes would carry clinic-b. The policy also needs to prevent users from changing or adding clinic tags to broaden access. A few pointers as you build it out: writeConstraint is the mechanism for that last part, and Binary is the one resource type the tag pattern won't cover—wrap it in a tagged DocumentReference and point securityContext at that. Worth noting this all works with a single shared Patient record, which is what makes the portal side easy: the patient's own access policy is scoped to their compartment rather than to clinic tags, so one login allows the patient to see their full chart across every clinic with no extra configuration.
k
thank you. This was very helpful
We went a different way and I'd like a sanity check. Instead of tagging notes, the shared Patient carries no clinic account at all (only itself), and each clinical resource gets its clinic from the writer's AccessPolicy.compartment at create. Nothing propagates, so we kept meta.accounts. Why we prefer it: canWriteAccount() is extendedMode && (superAdmin || projectAdmin), so a clinic user can't forge meta.account. A tag is a body field, so the same protection needs writeConstraint on every role and type. Three things: 1. Safe to lean on repo.ts ~2168 — writer's compartment applies at create only, so an edit can't pull a record into the editor's account? 2. Binary: is securityContext enforced reading the Binary directly, or only via the DocumentReference? 3. The shared Patient stays readable by every clinic. Any real boundary there, or is that inherent? App-side list filtering isn't one — GET /Patient/ still returns a stranger's demographics.
6 Views