2026-03-26 10:00:00+00:00

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:

FileMaker DB Relational Records FileMaker Data API Flat JSON Payloads TS Mapping Engine Flat-to-Hierarchical ETL FHIR R4 Server Medplum Repository

🧩 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:

  1. Demographics mapped to a Patient resource.
  2. Clinical diagnoses mapped to separate Condition resources linked via references.
  3. Specific details (like spasms, chronic pain levels, or lateralization) mapped to Observation resources 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:

  1. Eliminate Data Silos: Patient records are immediately readable by any FHIR-compliant third-party system, simplifying compliance and medical reporting.
  2. Strict Validation: Medplum enforces the FHIR R4 schema, blocking malformed FileMaker fields or broken dates before they corrupt downstream databases.
  3. 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.