Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Launch a JupyterHealth dashboard via SMART on FHIR (MedPlum example)

In this tutorial you stand up a provider-facing dashboard app that a clinician launches from an EHR. The EHR launch is the only login: the app exchanges the EHR’s id_token for a JupyterHealth Exchange token behind the scenes (see Provider EHR Launch), joins the launched patient to their JHE record by MRN, and renders that patient’s device data (CGM, wearables) in a Voilà notebook.

MedPlum plays the EHR here because its hosted sandbox is free and takes minutes to configure — but nothing below is MedPlum-specific: the same app launches from any SMART-enabled EHR by changing only registration values (see the template’s EHR registration guide for Epic notes, including its stricter scope grammar).

What you need:

1. Create the app from the template

Generate your app from jupyterhealth-sof-provider-template (“Use this template”, or clone it) and follow its QUICKSTART through the .env step. The values that matter for this tutorial:

JHE_URL=<your JHE base URL — must equal the JHE instance's SITE_URL exactly>
JHE_CLIENT_ID=<the JHE "SoF EHR Launch" client id>
JHE_CLIENT_SECRET=<its secret>
SMART_CLIENT_ID=<from step 2 below>
SMART_SCOPES=openid fhirUser launch patient/*.read
MRN_IDENTIFIER_SYSTEM=<any URI you choose — you control both sides; e.g. https://example.org/mrn>

Then run it: docker compose up --build — the app listens on http://localhost:8888 and does nothing until an EHR launches it.

2. Register the app in MedPlum

In your MedPlum project: Admin → Project → Clients → New, and set:

Copy the client’s ID into SMART_CLIENT_ID in your .env.

3. Configure JHE to trust the launch

On the JHE side (details):

4. Create the demo patient (the MRN join)

The app finds the JHE patient whose external identifier equals the EHR patient’s MRN value. Create a MedPlum Patient mirroring a JHE patient with data, e.g. the seeded CGM patient:

FieldValue
Name / birth datecopy from the JHE patient (e.g. May Nguyen, 1984-07-11)
Patient.identifier.systemyour MRN_IDENTIFIER_SYSTEM from step 1
Patient.identifier.valuethe JHE patient’s external id (e.g. 1636-69-001)

Name and birth date must match the JHE record — the app verifies the two records are the same person before showing anything (it fails closed with a lock notice if not). Identifier values must be equal; the identifier systems on each side need not agree.

5. Launch

In MedPlum, open the patient → Apps → click your app. MedPlum redirects through /smart-on-fhir/launch, asks you to authorize, and returns to the callback — at which point the app exchanges the id_token with JHE and renders the dashboard: the patient’s demographics header and their device data, fetched from JHE as you, under your normal JHE authorization. No JHE login screen ever appears.

On the JHE side you can watch it happen — the log records the exchange:

Token exchange: issued JHE token for Practitioner '<your id>' from issuer
https://api.medplum.com/ to client 'sof-ehr-launch'

If you get a lock notice or an error page instead, the QUICKSTART troubleshooting table maps each symptom to its cause — the failure point (trust config, practitioner mapping, MRN join) is always identifiable from which message you see.

Where to go from here