Back to Articles

HL7 Standards Guide: Understanding Healthcare Data Exchange

Health Level Seven (HL7) International is the global authority on healthcare interoperability standards. For over three decades, HL7 standards have enabled healthcare systems to communicate with each other, sharing everything from patient demographics to lab results to clinical documents.

This guide covers the HL7 standards you'll encounter in healthcare development: HL7 v2 (the workhorse of hospital integrations), HL7 v3 (the ambitious but complex successor), CDA (clinical documents), and FHIR (the modern web-friendly approach). Understanding when to use each standard is crucial for successful healthcare integrations.

The HL7 Standards Landscape

Before diving into specifics, let's understand how these standards relate to each other:

  • HL7 v2.x (1987-present): The most widely deployed healthcare messaging standard. Pipe-delimited messages for real-time clinical events.
  • HL7 v3 (2005-present): XML-based standard built on a Reference Information Model (RIM). Complex but precise.
  • CDA (2005-present): Clinical Document Architecture for sharing clinical documents (discharge summaries, progress notes).
  • FHIR (2014-present): Modern RESTful API approach using JSON/XML resources. The future of healthcare interoperability.
Reality Check

Despite FHIR's momentum, HL7 v2 remains dominant in hospital environments. Most lab systems, ADT (admission/discharge/transfer) feeds, and clinical interfaces still use v2 messages. Expect to work with multiple standards in any enterprise healthcare integration.

HL7 v2.x: The Hospital Workhorse

HL7 v2 messages power the backbone of hospital operations. When a patient is admitted, a lab result is ready, or a medication is ordered, v2 messages notify interested systems in real-time.

v2 Message Structure

HL7 v2 uses a pipe-delimited format with segments, fields, and components:

MSH|^~\&|LAB_SYSTEM|HOSPITAL|EMR|HOSPITAL|20260115120000||ORU^R01|MSG00001|P|2.5.1
PID|1||MRN12345^^^HOSP^MR||Smith^John^Robert||19700515|M|||123 Main St^^Boston^MA^02101
OBR|1||LAB123456|80053-8^Basic Metabolic Panel^LN|||20260115100000
OBX|1|NM|2345-7^Glucose^LN||95|mg/dL|70-100|N|||F
OBX|2|NM|2160-0^Creatinine^LN||1.1|mg/dL|0.7-1.3|N|||F
OBX|3|NM|2951-2^Sodium^LN||142|mmol/L|136-145|N|||F

Let's break down this structure:

  • MSH (Message Header): Message metadata—sending/receiving systems, timestamp, message type (ORU^R01 = Observation Result)
  • PID (Patient Identification): Patient demographics
  • OBR (Observation Request): The order/test being reported
  • OBX (Observation Result): Individual test results with values, units, reference ranges, and interpretation flags

Common HL7 v2 Message Types

// Common HL7 v2 message types in hospital environments

const HL7_MESSAGE_TYPES = {
  // ADT - Admission, Discharge, Transfer
  'ADT^A01': 'Patient Admit',
  'ADT^A02': 'Patient Transfer',
  'ADT^A03': 'Patient Discharge',
  'ADT^A04': 'Patient Registration',
  'ADT^A08': 'Patient Information Update',
  'ADT^A11': 'Cancel Admit',
  
  // ORM - Order Messages
  'ORM^O01': 'General Order',
  'RDE^O11': 'Pharmacy Order',
  'OMG^O19': 'General Clinical Order',
  
  // ORU - Observation Results
  'ORU^R01': 'Unsolicited Observation Result',
  'ORU^R30': 'Unsolicited Point-of-Care Observation',
  
  // SIU - Scheduling
  'SIU^S12': 'Notification of New Appointment',
  'SIU^S13': 'Notification of Appointment Reschedule',
  'SIU^S14': 'Notification of Appointment Modification',
  'SIU^S15': 'Notification of Appointment Cancellation',
  
  // MDM - Medical Document Management
  'MDM^T02': 'Document Status Change with Content',
  'MDM^T11': 'Document Cancel Notification',
  
  // DFT - Financial Transactions
  'DFT^P03': 'Post Detail Financial Transaction',
  'DFT^P11': 'Post Detail Financial Transaction - Expanded'
};

Parsing HL7 v2 Messages

// TypeScript HL7 v2 Parser
interface HL7Segment {
  name: string;
  fields: string[];
}

interface HL7Message {
  segments: HL7Segment[];
  messageType: string;
  messageControlId: string;
  timestamp: Date;
}

class HL7v2Parser {
  private fieldSeparator = '|';
  private componentSeparator = '^';
  private repetitionSeparator = '~';
  private escapeCharacter = '\\';
  private subcomponentSeparator = '&';

  parse(rawMessage: string): HL7Message {
    // Normalize line endings
    const lines = rawMessage
      .replace(/\r\n/g, '\n')
      .replace(/\r/g, '\n')
      .split('\n')
      .filter(line => line.trim());

    // Parse MSH to get encoding characters
    const mshLine = lines[0];
    if (!mshLine.startsWith('MSH')) {
      throw new Error('Invalid HL7 message: must start with MSH segment');
    }

    this.parseEncodingCharacters(mshLine);

    const segments = lines.map(line => this.parseSegment(line));
    const msh = segments[0];

    return {
      segments,
      messageType: this.getField(msh, 9),  // MSH-9
      messageControlId: this.getField(msh, 10),  // MSH-10
      timestamp: this.parseHL7DateTime(this.getField(msh, 7))  // MSH-7
    };
  }

  private parseEncodingCharacters(mshLine: string): void {
    // MSH-1 is field separator (|)
    // MSH-2 contains ^~\&
    this.fieldSeparator = mshLine[3];
    const encodingChars = mshLine.substring(4, 8);
    this.componentSeparator = encodingChars[0];
    this.repetitionSeparator = encodingChars[1];
    this.escapeCharacter = encodingChars[2];
    this.subcomponentSeparator = encodingChars[3];
  }

  private parseSegment(line: string): HL7Segment {
    const parts = line.split(this.fieldSeparator);
    const name = parts[0];
    
    // MSH is special - field separator counts as field 1
    const fields = name === 'MSH' 
      ? [this.fieldSeparator, ...parts.slice(1)]
      : parts.slice(1);

    return { name, fields };
  }

  private getField(segment: HL7Segment, index: number): string {
    return segment.fields[index - 1] || '';
  }

  getComponent(field: string, index: number): string {
    const components = field.split(this.componentSeparator);
    return components[index - 1] || '';
  }

  private parseHL7DateTime(dt: string): Date {
    // HL7 datetime format: YYYYMMDDHHMMSS
    if (dt.length < 8) return new Date();
    
    const year = parseInt(dt.substring(0, 4));
    const month = parseInt(dt.substring(4, 6)) - 1;
    const day = parseInt(dt.substring(6, 8));
    const hour = dt.length >= 10 ? parseInt(dt.substring(8, 10)) : 0;
    const minute = dt.length >= 12 ? parseInt(dt.substring(10, 12)) : 0;
    const second = dt.length >= 14 ? parseInt(dt.substring(12, 14)) : 0;

    return new Date(year, month, day, hour, minute, second);
  }

  // Extract patient from PID segment
  extractPatient(message: HL7Message): PatientData {
    const pid = message.segments.find(s => s.name === 'PID');
    if (!pid) throw new Error('No PID segment found');

    const patientId = this.getField(pid, 3);
    const patientName = this.getField(pid, 5);
    const dob = this.getField(pid, 7);
    const gender = this.getField(pid, 8);
    const address = this.getField(pid, 11);

    return {
      mrn: this.getComponent(patientId, 1),
      lastName: this.getComponent(patientName, 1),
      firstName: this.getComponent(patientName, 2),
      middleName: this.getComponent(patientName, 3),
      dateOfBirth: this.parseHL7DateTime(dob),
      gender: gender,
      address: {
        street: this.getComponent(address, 1),
        city: this.getComponent(address, 3),
        state: this.getComponent(address, 4),
        zip: this.getComponent(address, 5)
      }
    };
  }
}

HL7 v3 and the Reference Information Model

HL7 v3 attempted to solve v2's ambiguity with a formal model-based approach. It's built on the Reference Information Model (RIM)—a comprehensive model of all healthcare information.

v3 XML Structure

<?xml version="1.0" encoding="UTF-8"?>
<PRPA_IN201301UV02 xmlns="urn:hl7-org:v3" 
  ITSVersion="XML_1.0">
  <id root="2.16.840.1.113883.19.1122.7" extension="CNTRL-3456"/>
  <creationTime value="20260115120000"/>
  <interactionId root="2.16.840.1.113883.1.6" extension="PRPA_IN201301UV02"/>
  <processingCode code="P"/>
  <processingModeCode code="T"/>
  <acceptAckCode code="AL"/>
  
  <receiver typeCode="RCV">
    <device classCode="DEV" determinerCode="INSTANCE">
      <id root="2.16.840.1.113883.19.1122.1" extension="EMR_SYSTEM"/>
    </device>
  </receiver>
  
  <sender typeCode="SND">
    <device classCode="DEV" determinerCode="INSTANCE">
      <id root="2.16.840.1.113883.19.1122.1" extension="ADT_SYSTEM"/>
    </device>
  </sender>
  
  <controlActProcess classCode="CACT" moodCode="EVN">
    <subject typeCode="SUBJ">
      <registrationEvent classCode="REG" moodCode="EVN">
        <subject1 typeCode="SBJ">
          <patient classCode="PAT">
            <id root="2.16.840.1.113883.19.5" extension="MRN12345"/>
            <statusCode code="active"/>
            <patientPerson classCode="PSN" determinerCode="INSTANCE">
              <name>
                <given>John</given>
                <family>Smith</family>
              </name>
              <administrativeGenderCode code="M" 
                codeSystem="2.16.840.1.113883.5.1"/>
              <birthTime value="19700515"/>
            </patientPerson>
          </patient>
        </subject1>
      </registrationEvent>
    </subject>
  </controlActProcess>
</PRPA_IN201301UV02>
v3 Adoption Reality

HL7 v3 saw limited adoption due to its complexity. Most implementations focus on specific v3-based standards like CDA (Clinical Document Architecture) rather than full v3 messaging. If you're starting a new integration today, consider FHIR unless you're specifically required to use v3.

CDA: Clinical Document Architecture

CDA is an HL7 v3-based standard for clinical documents. Unlike event-driven messages, CDA represents complete, signed clinical documents like discharge summaries, progress notes, or consultation reports.

CDA Document Structure

<?xml version="1.0" encoding="UTF-8"?>
<ClinicalDocument xmlns="urn:hl7-org:v3">
  <!-- Header -->
  <typeId root="2.16.840.1.113883.1.3" extension="POCD_HD000040"/>
  <templateId root="2.16.840.1.113883.10.20.22.1.2"/>
  <id root="2.16.840.1.113883.19.5.99999.1" extension="TT988"/>
  <code code="34133-9" displayName="Summarization of Episode Note"
    codeSystem="2.16.840.1.113883.6.1" codeSystemName="LOINC"/>
  <title>Patient Summary</title>
  <effectiveTime value="20260115"/>
  <confidentialityCode code="N" codeSystem="2.16.840.1.113883.5.25"/>
  <languageCode code="en-US"/>
  
  <!-- Patient -->
  <recordTarget>
    <patientRole>
      <id root="2.16.840.1.113883.19.5" extension="MRN12345"/>
      <patient>
        <name>
          <given>John</given>
          <family>Smith</family>
        </name>
        <administrativeGenderCode code="M" 
          codeSystem="2.16.840.1.113883.5.1"/>
        <birthTime value="19700515"/>
      </patient>
    </patientRole>
  </recordTarget>
  
  <!-- Author -->
  <author>
    <time value="20260115120000"/>
    <assignedAuthor>
      <id root="2.16.840.1.113883.4.6" extension="1234567890"/>
      <assignedPerson>
        <name>
          <given>Sarah</given>
          <family>Johnson</family>
          <suffix>MD</suffix>
        </name>
      </assignedPerson>
    </assignedAuthor>
  </author>
  
  <!-- Body -->
  <component>
    <structuredBody>
      <!-- Problems Section -->
      <component>
        <section>
          <templateId root="2.16.840.1.113883.10.20.22.2.5.1"/>
          <code code="11450-4" codeSystem="2.16.840.1.113883.6.1"
            displayName="Problem List"/>
          <title>Problems</title>
          <text>
            <list>
              <item>Essential Hypertension (Active)</item>
              <item>Type 2 Diabetes Mellitus (Active)</item>
            </list>
          </text>
          <entry>
            <act classCode="ACT" moodCode="EVN">
              <!-- Structured problem data -->
            </act>
          </entry>
        </section>
      </component>
      
      <!-- Medications Section -->
      <component>
        <section>
          <templateId root="2.16.840.1.113883.10.20.22.2.1.1"/>
          <code code="10160-0" codeSystem="2.16.840.1.113883.6.1"
            displayName="Medications"/>
          <title>Medications</title>
          <text>
            <table>
              <thead>
                <tr>
                  <th>Medication</th>
                  <th>Dosage</th>
                  <th>Frequency</th>
                </tr>
              </thead>
              <tbody>
                <tr>
                  <td>Lisinopril</td>
                  <td>10 mg</td>
                  <td>Once daily</td>
                </tr>
                <tr>
                  <td>Metformin</td>
                  <td>500 mg</td>
                  <td>Twice daily</td>
                </tr>
              </tbody>
            </table>
          </text>
        </section>
      </component>
    </structuredBody>
  </component>
</ClinicalDocument>

C-CDA: Consolidated CDA

Consolidated CDA (C-CDA) is the U.S. implementation of CDA, mandated for Meaningful Use and USCDI. It defines specific document types and section templates:

// C-CDA Document Types
const CCDA_DOCUMENT_TYPES = {
  // Care summaries
  'CCD': {
    loinc: '34133-9',
    name: 'Continuity of Care Document',
    description: 'Summary of patient health information'
  },
  'DISCHARGE_SUMMARY': {
    loinc: '18842-5',
    name: 'Discharge Summary',
    description: 'Summary at hospital discharge'
  },
  'PROGRESS_NOTE': {
    loinc: '11506-3',
    name: 'Progress Note',
    description: 'Ongoing care documentation'
  },
  'CONSULTATION_NOTE': {
    loinc: '11488-4',
    name: 'Consultation Note',
    description: 'Specialist consultation report'
  },
  'HISTORY_AND_PHYSICAL': {
    loinc: '34117-2',
    name: 'History and Physical',
    description: 'Initial patient assessment'
  },
  'OPERATIVE_NOTE': {
    loinc: '11504-8',
    name: 'Operative Note',
    description: 'Surgical procedure documentation'
  },
  'REFERRAL_NOTE': {
    loinc: '57133-1',
    name: 'Referral Note',
    description: 'Referral to another provider'
  }
};

// Required C-CDA sections for CCD
const CCD_REQUIRED_SECTIONS = [
  'Allergies and Intolerances',
  'Medications',
  'Problems',
  'Procedures',
  'Results',
  'Social History',
  'Vital Signs'
];

Choosing the Right Standard

Each HL7 standard serves different use cases. Here's a decision framework:

Use HL7 v2 When:

  • Integrating with legacy hospital systems (ADT, labs, radiology)
  • Real-time event notification is required
  • The trading partner only supports v2
  • High message volume with low latency requirements

Use CDA/C-CDA When:

  • Exchanging clinical documents (summaries, notes, reports)
  • Regulatory compliance requires C-CDA (USCDI, Meaningful Use)
  • Documents need to be human-readable AND machine-processable
  • Long-term document storage and retrieval

Use FHIR When:

  • Building new applications or APIs
  • Mobile and web application development
  • Patient-facing applications (patient portals, PHR)
  • Modern interoperability requirements (21st Century Cures)
  • Integration with EHRs that support SMART on FHIR
// Integration pattern decision tree
function selectIntegrationStandard(requirements: IntegrationRequirements): Standard {
  // Regulatory requirements first
  if (requirements.requiresUSCDI || requirements.requiresMeaningfulUse) {
    if (requirements.dataType === 'document') {
      return 'C-CDA';
    }
  }
  
  // Legacy system integration
  if (requirements.targetSystem.isLegacy) {
    if (requirements.targetSystem.supportedStandards.includes('HL7v2')) {
      return 'HL7v2';
    }
  }
  
  // New development
  if (requirements.isNewDevelopment) {
    if (requirements.targetSystem.supportsFHIR) {
      return 'FHIR';
    }
  }
  
  // Real-time events
  if (requirements.isRealTime && requirements.eventDriven) {
    return 'HL7v2';  // Still the standard for ADT, orders, results
  }
  
  // Clinical documents
  if (requirements.dataType === 'document') {
    return 'C-CDA';
  }
  
  // Default to FHIR for new integrations
  return 'FHIR';
}

Terminology and Code Systems

All HL7 standards rely on standardized terminologies for consistent meaning:

// Common healthcare terminologies
const CODE_SYSTEMS = {
  // Clinical terminologies
  'SNOMED-CT': {
    oid: '2.16.840.1.113883.6.96',
    name: 'Systematized Nomenclature of Medicine',
    use: 'Clinical findings, procedures, body structures'
  },
  'LOINC': {
    oid: '2.16.840.1.113883.6.1',
    name: 'Logical Observation Identifiers Names and Codes',
    use: 'Laboratory tests, clinical observations, documents'
  },
  'ICD-10-CM': {
    oid: '2.16.840.1.113883.6.90',
    name: 'International Classification of Diseases',
    use: 'Diagnoses, conditions'
  },
  'CPT': {
    oid: '2.16.840.1.113883.6.12',
    name: 'Current Procedural Terminology',
    use: 'Medical procedures (billing)'
  },
  'RxNorm': {
    oid: '2.16.840.1.113883.6.88',
    name: 'RxNorm',
    use: 'Medications'
  },
  'NDC': {
    oid: '2.16.840.1.113883.6.69',
    name: 'National Drug Code',
    use: 'Drug products (FDA)'
  },
  'CVX': {
    oid: '2.16.840.1.113883.12.292',
    name: 'Vaccine Administered',
    use: 'Immunizations'
  }
};

// Example coded value
const bloodPressureCode = {
  system: 'http://loinc.org',  // FHIR URI format
  code: '85354-9',
  display: 'Blood pressure panel with all children optional'
};
Key Takeaway

Healthcare interoperability requires understanding multiple standards. While FHIR represents the future, HL7 v2 and CDA remain essential for hospital integrations and regulatory compliance. The best healthcare developers are fluent in all of them.

Next Steps

Start by identifying which systems you need to integrate with and what standards they support. For new FHIR implementations, see our detailed FHIR Integration Guide. For security considerations across all healthcare integrations, review our HIPAA Compliance Guide.