Hospital Integrations
Routes: /hospitals, /hospitals/new, /hospitals/:id
Files: src/hospitals/HospitalsPage.tsx, src/hospitals/HospitalIntegrationPage.tsx
Hospital Integrations register EHR endpoints so patients can connect via SMART on FHIR OAuth. Each entry is stored as a standard FHIR Endpoint resource with vendor-specific data encoded in extensions.
Hospital List (/hospitals)​
File: HospitalsPage.tsx
Fetches Endpoint resources via medplum.search('Endpoint', ...) with:
_sort: '-_lastUpdated'_count: 20(paginated, max offset 10,000)_total: 'accurate'
Filters​
| Filter | FHIR Search Param | Notes |
|---|---|---|
| Name search | name:contains | Applied on Enter or search button click |
| EHR Vendor | ehr-vendor | Custom search param on the Endpoint resource |
| Status | status | active or suspended |
Table columns​
| Column | Source | Notes |
|---|---|---|
| Name | Endpoint.name | |
| EHR Vendor | extension[url=.../endpoint-ehr-vendor].valueCode | Blue = epic, Teal = healow, Violet = meditech |
| FHIR Base URL | Endpoint.address | Truncated at 320px |
| Status toggle | Endpoint.status | active → suspended toggle via medplum.updateResource() |
| Edit | — | Navigates to /hospitals/:id |
| Delete | — | Shows confirmation modal, then medplum.deleteResource('Endpoint', id) — hard delete |
Create / Edit Hospital (/hospitals/new, /hospitals/:id)​
File: HospitalIntegrationPage.tsx
In edit mode, loads the Endpoint resource via medplum.readResource('Endpoint', id) and maps fields back from extensions. On save:
- Create:
medplum.createResource(endpoint)→ redirects to/hospitals - Edit:
medplum.updateResource(endpoint)→ redirects to/hospitals
Form Fields​
Hospital Name​
Required.
Maps to Endpoint.name. This is the display name shown to patients in the member portal when they browse hospitals to connect. Use the official name of the hospital or health system.
EHR Vendor​
Required.
Stored as extension[url = 'http://devapi.chartchathealth.com/fhir/StructureDefinition/endpoint-ehr-vendor'].valueCode.
| Value | Label | Notes |
|---|---|---|
epic | Epic | Most common. Uses PKCE, no client secret required. |
healow | Healow (eClinicalWorks) | Requires Practice Code field. |
meditech | MEDITECH | Requires Client ID field. Client Secret optional. |
Selecting a vendor dynamically shows/hides conditional fields (Practice Code for Healow, Client ID / Client Secret for MEDITECH).
FHIR Base URL​
Required.
Maps to Endpoint.address. This is the root URL of the hospital's FHIR R4 API, e.g. https://fhir.example.org/api/FHIR/R4.
The URL must be a valid URL (validated client-side). The SMART discovery endpoint is derived from this URL during the Test Connection check.
Stored in Endpoint.address.
Test SMART Discovery button​
Calls:
GET fhir/ehr-import/smart-discovery?url=<encoded fhirBaseUrl>
The server fetches the SMART configuration document (.well-known/smart-configuration or /metadata) at that URL and returns authorization_endpoint and token_endpoint. On success the UI shows the discovered authorization URL in a green alert. On failure it shows the error message in red.
Always run this before saving a new hospital. If discovery fails, the patient OAuth flow will also fail.
Practice Code​
Shown only when EHR Vendor = Healow. Required for Healow.
Stored in modifierExtension[url='practiceCode'].valueString.
This is the Healow/eClinicalWorks-specific practice identifier that is embedded in the FHIR base URL and used during the authorization flow. It is provided by eClinicalWorks when they set up your integration. Example: JAFJCD.
Client ID​
Shown only when EHR Vendor = MEDITECH. Required for MEDITECH.
Stored in modifierExtension[url='clientId'].valueString.
The OAuth client ID issued by the hospital's MEDITECH system when your app was registered. MEDITECH issues a unique client ID per hospital.
Client Secret​
Shown only when EHR Vendor = MEDITECH. Optional.
Stored in modifierExtension[url='clientSecret'].valueString.
Only required if the hospital configured your app as a confidential client in MEDITECH. Most MEDITECH public-client setups do not require this. Check with the hospital's IT team.
OAuth Scope​
Optional.
Stored in modifierExtension[url='scope'].valueString.
Space-separated list of SMART on FHIR scopes requested during the patient OAuth authorization. If left blank, the member portal uses its built-in default scopes for that vendor.
Example:
launch/patient patient/*.read openid fhirUser offline_access
Common scopes:
| Scope | Purpose |
|---|---|
launch/patient | Request patient context at launch |
patient/*.read | Read all patient-level resources |
openid | Include identity token |
fhirUser | Include the FHIR user reference in the token |
offline_access | Get a refresh token for background syncs |
Redirect URI​
Optional — overrides the vendor default.
Stored in modifierExtension[url='redirectUri'].valueUri.
The URL the EHR's authorization server redirects the patient back to after they approve access. In most deployments the member portal has one configured redirect URI per EHR vendor already built in. Only set this field if you need to override it for a specific hospital.
Logo URL​
Optional.
Stored in extension[url='logoUrl'].valueUrl.
A publicly accessible URL to the hospital's logo image. Displayed in the member portal when the patient browses hospital connections. Should be a square or landscape image at least 64×64px.
Enabled​
Required (checkbox, default: checked).
Maps to Endpoint.status: active when checked, suspended when unchecked.
Only hospitals with status = active are visible to patients in the member portal. Use this to register a hospital without making it available yet (e.g. during testing or before credentials are finalised).
How Fields Are Stored on the Endpoint Resource​
The FHIR Endpoint resource splits fields into two categories:
| Category | Extension type | Readable by patients? | Fields |
|---|---|---|---|
| Public | extension[] | Yes (via access policy) | ehrVendor, logoUrl |
| Private | modifierExtension[] | No (hidden by access policy) | clientId, clientSecret, practiceCode, scope, redirectUri |
The modifierExtension mechanism means the server's access policy strips these fields from any response to patient-level tokens, so OAuth credentials are never exposed to the member portal frontend.
The full resource structure after save:
{
"resourceType": "Endpoint",
"status": "active",
"name": "Northwell Health",
"address": "https://fhir.example.org/api/FHIR/R4",
"connectionType": {
"system": "http://terminology.hl7.org/CodeSystem/endpoint-connection-type",
"code": "hl7-fhir-rest"
},
"payloadType": [{ "coding": [{ "code": "not-applicable" }] }],
"extension": [
{ "url": "http://devapi.chartchathealth.com/fhir/StructureDefinition/endpoint-ehr-vendor", "valueCode": "epic" },
{ "url": "logoUrl", "valueUrl": "https://cdn.example.com/logo.png" }
],
"modifierExtension": [
{ "url": "clientId", "valueString": "abc-123" },
{ "url": "scope", "valueString": "launch/patient patient/*.read openid fhirUser offline_access" },
{ "url": "redirectUri", "valueUri": "https://app.chartchathealth.com/ehr/callback" }
]
}