In modern healthcare IT, interoperability is the holy grail. The industry has standardized around FHIR R4 (Fast Healthcare Interoperability Resources) as the universal language for sharing electronic health records.
However, in many real-world clinical and rehabilitation clinics, legacy systems like FileMaker Pro remain highly popular. Clinical teams love FileMaker because it allows them to build highly specialized relational tables, forms, and layouts tailored to custom patient intake workflows (such as orthopedic assessments, spasm scales, and medical device telemetry).
The challenge is bridging this gap. How do we extract flat, customized clinical records from FileMaker layouts and transform them into structurally valid, standard FHIR resources?
This article details how to build a robust FHIR R4 ETL Mapping Engine in Node.js, utilizing the FileMaker Data API and Medplum.
🏗️ The Architectural Pipeline
To build a seamless, secure synchronization pipeline, we map FileMaker layouts directly to standardized FHIR endpoints:
🧩 The Schema Translation Challenge
FileMaker models data in flat structures designed for layout forms. For example, a single patient record might contain direct checkbox fields like Injury_Stroke (True/False) or Injury_Stroke_AffectedSide_Left (True/False).
In FHIR R4, this information is highly structured:
- Demographics mapped to a
Patientresource. - Clinical diagnoses mapped to separate
Conditionresources linked via references. - Specific details (like spasms, chronic pain levels, or lateralization) mapped to
Observationresources with distinct LOINC or SNOMED CT codes.
🛠️ The Node.js Mapping Implementation
Below is a robust TypeScript mapper that pulls raw JSON payloads from the FileMaker Data API, structures the nested attributes, and maps them to standard FHIR R4 JSON schemas:
// filemaker-fhir-mapper.ts (FileMaker-to-FHIR R4 Conversion Engine)
import { Patient, Observation, Condition } from 'fhir/r4';
export interface FileMakerPatientRecord {
// Demographics
Patient_ID: string;
Name_First: string;
Name_Last: string;
BirthDate: string; // YYYY-MM-DD
Gender: string;
Contact_Email: string;
Contact_Phone: string;
// Clinical Conditions
Injury_Stroke: boolean;
Injury_Stroke_AffectedSide_Left?: boolean;
Injury_Stroke_AffectedSide_Right?: boolean;
Injury_Spasms?: boolean;
Injury_Spasms_Severity?: number; // 0 to 10
}
/**
* Maps a flat FileMaker patient record into a clean FHIR Patient Resource
*/
export function mapFileMakerToFhirPatient(fmRecord: FileMakerPatientRecord): Patient {
return {
resourceType: 'Patient',
id: `pat-${fmRecord.Patient_ID}`,
identifier: [
{
system: 'https://axiobionics.com/identifiers/patient',
value: fmRecord.Patient_ID
}
],
name: [
{
use: 'official',
family: fmRecord.Name_Last,
given: [fmRecord.Name_First]
}
],
birthDate: fmRecord.BirthDate,
gender: mapGender(fmRecord.Gender),
telecom: [
{
system: 'phone',
value: fmRecord.Contact_Phone,
use: 'mobile'
},
{
system: 'email',
value: fmRecord.Contact_Email
}
]
};
}
/**
* Maps specific neurological diagnoses into separate FHIR Condition resources
*/
export function mapFileMakerToFhirCondition(
fmRecord: FileMakerPatientRecord,
patientRef: string
): Condition | null {
if (!fmRecord.Injury_Stroke) {
return null;
}
// Build stroke clinical condition using SNOMED CT codes
return {
resourceType: 'Condition',
id: `cond-stroke-${fmRecord.Patient_ID}`,
subject: { reference: patientRef },
clinicalStatus: {
coding: [{ system: 'http://terminology.hl7.org/CodeSystem/condition-clinical', code: 'active' }]
},
code: {
coding: [
{
system: 'http://snomed.info/sct',
code: '230690007',
display: 'Cerebrovascular accident (Stroke)'
}
],
text: 'Stroke'
},
bodySite: mapAffectedSide(fmRecord.Injury_Stroke_AffectedSide_Left, fmRecord.Injury_Stroke_AffectedSide_Right)
};
}
/**
* Maps spasm metrics into standard FHIR Observations using LOINC codes
*/
export function mapFileMakerToSpasmObservation(
fmRecord: FileMakerPatientRecord,
patientRef: string
): Observation | null {
if (!fmRecord.Injury_Spasms || fmRecord.Injury_Spasms_Severity === undefined) {
return null;
}
return {
resourceType: 'Observation',
id: `obs-spasms-${fmRecord.Patient_ID}`,
status: 'final',
category: [
{
coding: [{ system: 'http://terminology.hl7.org/CodeSystem/observation-category', code: 'exam' }]
}
],
code: {
coding: [
{
system: 'http://loinc.org',
code: '77292-1',
display: 'Spasm severity index'
}
],
text: 'Spasm Severity'
},
subject: { reference: patientRef },
valueInteger: fmRecord.Injury_Spasms_Severity
};
}
// Internal Helper Functions
function mapGender(gender: string): 'male' | 'female' | 'other' | 'unknown' {
const g = gender.toLowerCase().trim();
if (g === 'male' || g === 'm') return 'male';
if (g === 'female' || g === 'f') return 'female';
if (g === 'other' || g === 'o') return 'other';
return 'unknown';
}
function mapAffectedSide(left?: boolean, right?: boolean): any[] {
const sites: any[] = [];
if (left) {
sites.push({
coding: [{ system: 'http://snomed.info/sct', code: '7771000', display: 'Left' }]
});
}
if (right) {
sites.push({
coding: [{ system: 'http://snomed.info/sct', code: '24028007', display: 'Right' }]
});
}
return sites;
}
📈 Summary of Benefits
Mapping FileMaker relational layouts to standardized FHIR schemas unlocks massive institutional value:
- Eliminate Data Silos: Patient records are immediately readable by any FHIR-compliant third-party system, simplifying compliance and medical reporting.
- Strict Validation: Medplum enforces the FHIR R4 schema, blocking malformed FileMaker fields or broken dates before they corrupt downstream databases.
- Decoupled Modernization: Clinical staff continue using their favorite FileMaker dashboard layouts, while modern frontend developers build React portals fetching native FHIR endpoints.
By building an asynchronous node mapping pipeline, you bridge the gap between user-friendly desktop clinical software and enterprise-grade, interoperable web technology.