HL7 v2 to FHIR Conversion: The Complete Technical and Strategic Guide

Healthcare interoperability has historically relied on HL7 v2, a messaging standard created in the 1980s. These messages are compact, pipe-delimited, and event-driven (e.g., Admit, Discharge, Transfer events). But with the shift toward mobile apps, cloud platforms, and modern REST APIs, FHIR (Fast Healthcare Interoperability Resources) has become the go-to standard for exchanging healthcare data in a structured, API-first way.

Bridging HL7 v2 to FHIR conversion is critical for hospitals, EHR vendors, and startups that need to support legacy systems while exposing modern APIs.

This guide explores the why, how, and practical details of HL7 v2 to FHIR conversion, with a TypeScript-based code example for implementers.

Why Convert HL7 v2 to FHIR?

HL7 v2 to FHIR conversion enables organizations to modernize healthcare data exchange while maintaining backward compatibility.

  • Legacy compatibility: Many hospitals still rely on HL7 v2 feeds (ADT, ORU, ORM, SIU).
  • Modernization: Expose data as FHIR APIs for mobile apps, analytics, and cloud workflows.
  • Standards compliance: Regulatory pushes (like the US ONC Cures Act) favor FHIR APIs.
  • Interoperability: Vendors can integrate across systems more easily with FHIR JSON.

HL7 v2 Basics

Example ADT^A01 (Admit/Visit Notification) message:

MSH|^~\&|HOSP|HOSPIS|EHR|EHR|20250924||ADT^A01|MSG00001|P|2.5

EVN|A01|20250924

PID|1|12345^^^HOSP^MR||DOE^JOHN^A||19800101|M||2106-3|123 MAIN ST^^MUMBAI^MH^400001|MH|(999)999-9999|||S||123456789|987-65-4321

Key components:

  • MSH (Message Header): Message metadata (event type, sending/receiving apps).
  • EVN (Event): Event code and timestamp.
  • PID (Patient Identification): Patient demographics.

FHIR Basics

FHIR resources are structured JSON/XML objects with standardized schemas.
For example, during HL7 v2 to FHIR conversion, the HL7 v2 PID segment maps to a FHIR Patient resource.

Example FHIR Patient JSON:

{

  "resourceType": "Patient",

  "identifier": [

    { "system": "urn:oid:1.2.3.4.5.6.7.8", "value": "12345" }

  ],

  "name": [

    { "family": "DOE", "given": ["JOHN", "A"] }

  ],

  "gender": "male",

  "birthDate": "1980-01-01",

  "address": [

    { "line": ["123 MAIN ST"], "city": "MUMBAI", "state": "MH", "postalCode": "400001" }

  ]

}

Common Mapping Patterns

Typical mappings in HL7 v2 to FHIR conversion include:

  • PID → Patient
  • PV1 → Encounter
  • OBR → DiagnosticReport
  • OBX → Observation
  • ORC / RXA → Medication Request / Medication Administration

Challenges & Gotchas

Implementing HL7 v2 to FHIR conversion requires attention to key technical differences:

  • Event-driven vs. stateful data: HL7 v2 events (e.g., Admit) must be translated into FHIR resources with create/update logic.
  • Identifiers & duplicates: Different hospitals may use different identifier systems—use both system and value.
  • Terminology: HL7 v2 sometimes uses local codes; map them to standard FHIR codes (SNOMED, LOINC, ICD).
  • Custom Z-segments: Decide how to represent Z-segments in FHIR (extensions or ignore).
  • Date/time formats: HL7 v2 uses YYYY-MM-DD; FHIR expects ISO8601.

Ready to Build Your Own HL7 v2 to FHIR Pipeline?

Detailed TypeScript Example

Below is a TypeScript implementation that demonstrates the HL7 v2 to FHIR conversion process for a Patient resource.

Setup

npm install hl7-standard fhir-kit-client date-fns

Conversion Script

import { parseHL7Message, Segment } from 'hl7-standard';
import { parse } from 'date-fns';

// Define minimal FHIR types for Patient (could also use @types/fhir if needed)
interface Identifier {
system: string;
value: string;
}

interface HumanName {
family?: string;
given?: string[];
}

interface Address {
line?: string[];
city?: string;
state?: string;
postalCode?: string;
}

interface Patient {
resourceType: 'Patient';
identifier?: Identifier[];
name?: HumanName[];
gender?: string;
birthDate?: string;
address?: Address[];
}

// Sample HL7 message
const hl7Message = `MSH|^~\\&|HOSP|HOSPIS|EHR|EHR|20250924||ADT^A01|MSG00001|P|2.5\nEVN|A01|20250924\nPID|1|12345^^^HOSP^MR||DOE^JOHN^A||19800101|M||2106-3|123 MAIN ST^^MUMBAI^MH^400001|MH|(999)999-9999|||S||123456789|987-65-4321`;

const msg = parseHL7Message(hl7Message);
const pid: Segment = msg.getSegment('PID');

const patient: Patient = { resourceType: 'Patient' };

// Identifier (MRN)
const mrn = pid.getField(3)?.toString();
if (mrn) {
patient.identifier = [{ system: 'urn:oid:1.2.3.4.5.6.7.8', value: mrn }];
}

// Name
const nameRaw = pid.getField(5)?.toString();
if (nameRaw) {
const parts = nameRaw.split('^');
patient.name = [{ family: parts[0], given: [parts[1], parts[2]].filter(Boolean) }];
}

// Gender
const gender = pid.getField(8)?.toString();
const gmap: Record<string, string> = { M: 'male', F: 'female', O: 'other', U: 'unknown' };
patient.gender = gender ? gmap[gender.toUpperCase()] : 'unknown';

// Birthdate
const dob = pid.getField(7)?.toString();
if (dob) patient.birthDate = parse(dob, 'yyyyMMdd', new Date()).toISOString().split('T')[0];

// Address
const addrRaw = pid.getField(11)?.toString();
if (addrRaw) {
const parts = addrRaw.split('^');
patient.address = [{ line: [parts[0]], city: parts[2], state: parts[3], postalCode: parts[4] }];
}

console.log(JSON.stringify(patient, null, 2));

Output

{
"resourceType": "Patient",
"identifier": [
{ "system": "urn:oid:1.2.3.4.5.6.7.8", "value": "12345" }
],
"name": [
{ "family": "DOE", "given": ["JOHN", "A"] }
],
"gender": "male",
"birthDate": "1980-01-01",
"address": [
{ "line": ["123 MAIN ST"], "city": "MUMBAI", "state": "MH", "postalCode": "400001" }
]
}

Error Handling & Logging

For reliable HL7 v2 to FHIR conversion, include:

  • Validation: Use FHIR JSON schemas to validate converted resources.
  • Logging: Always log the original HL7 v2 MSH.10 alongside FHIR resource IDs.
  • Error resilience: For missing or invalid fields, decide whether to drop, default, or log.

Deployment Considerations

When scaling HL7 v2 to FHIR conversion pipelines:

  • Streaming ingestion: Use Kafka/NATS for real-time ingestion and FHIR JSON downstream.
  • Batch ETL: Run nightly conversions for bulk data in data lakes.
  • Persistence: Store data in a FHIR server (e.g., HAPI-FHIR, Google Cloud Healthcare API).
  • Extensibility: Add more HL7 v2 segments incrementally.

Best Practices

Adopt these best practices for efficient HL7 v2 to FHIR conversion:

  • Keep mappings externalized (YAML/JSON).
  • Start with ADT feed (PID + PV1), then expand.
  • Use terminology servers for local code mapping.
  • Handle versioning across HL7 v2.x standards.
    coma

    Conclusion

    HL7 v2 to FHIR conversion is not just a technical mapping exercise—it’s a strategic bridge between legacy systems and modern healthcare ecosystems. By carefully mapping, validating, and deploying a robust transformation pipeline, organizations can modernize their interoperability stack while ensuring compliance and future readiness.

    Keep Reading

    Let’s Transform
    Healthcare,
    Together.

    Partner with us to design, build, and scale digital solutions that drive better outcomes.

    Contact form