Taction Software — FHIR Integration with Mirth Connect
Blog·May 12, 2026·Taction Software

HL7 v2 to FHIR R4 Mapping Reference — A Complete Segment-by-Segment Guide

Every common HL7 v2 segment mapped to its FHIR R4 equivalent, with code examples you can adapt directly. ADT, ORU, ORM, and SIU coverage — PID → Patient, OBX → Observation, ORC → ServiceRequest, and the rest.

HL7FHIR R4MappingIntegrationReference
TL;DR

HL7 v2 segments map to FHIR R4 resources by event-to-state translation: PID → Patient, PV1 → Encounter, OBR → ServiceRequest + DiagnosticReport, OBX → Observation, ORC → ServiceRequest, AL1 → AllergyIntolerance, DG1 → Condition, PR1 → Procedure, SCH → Appointment, IN1 → Coverage. Wrap the resulting resources in a FHIR Bundle, cross-reference them by URL, and remember the three tensions — positional vs. named, inline vs. reference, message stream vs. resource model — that produce most real-world mapping bugs.

Why this reference exists

If you've worked in healthcare integration for any length of time, you've hit this problem. You have a system that speaks HL7 v2. You have another system that speaks FHIR R4. Somewhere between them you need to translate one to the other, and the official documentation reads like it was written for people who already know the answer.

This is the reference we wish existed when we started doing this work. Every common HL7 v2 segment, mapped to its FHIR R4 equivalent, with code examples you can adapt directly. Bookmark it. Send it to your team. Use it as the source of truth when someone on a standup says “wait, what's MSH supposed to map to?”

This guide assumes basic familiarity with both standards. If HL7 v2 segments are still new, start with HL7 v2 message structure explained, and for the FHIR side see the FHIR R4 integration complete guide. Backed by the engineers who deliver Mirth Connect support for US healthcare organizations.

Why HL7 v2 to FHIR mapping is harder than it looks

The official IHE and HL7 mapping documents will tell you that PID maps to Patient, OBX maps to Observation, and PV1 maps to Encounter. That's true but incomplete.

The real complexity isn't in the segment-to-resource mapping. It's in three things the docs gloss over.

First, HL7 v2 is positional and FHIR is named. PID-5 is the patient's name because it's in the fifth position of the PID segment, not because it has a label. FHIR's Patient.nameis named explicitly. Anywhere you have field-level transformations, you're translating between positional thinking and named-property thinking, and that's where most bugs live.

Second, HL7 v2 carries identity inline and FHIR carries identity by reference.In HL7, the patient's name appears in every message that mentions them. In FHIR, the patient is a separate resource and every message references them by URL. Translating from inline-identity to reference-identity means you need to either look up or create the patient resource before you can create the observation that points to them.

Third, HL7 v2 is a stream of messages and FHIR is a model of resources. A single HL7 v2 ORUmessage containing 5 lab results becomes, in FHIR, a Bundle containing 1 Patient + 1 Encounter + 1 ServiceRequest + 5 Observations + (potentially) 1 DiagnosticReport that references all the observations. The cardinality doesn't match.

Keep all three of these tensions in mind as you read the rest of this reference. Most mapping bugs trace back to one of them.

The mental model: messages vs. resources

Before getting into segment-by-segment detail, internalize this:

HL7 v2 message = a verb.“An admission happened.” “A lab result is ready.” “An appointment was scheduled.” The message is event-oriented.

FHIR resource = a noun.“Here is a patient.” “Here is an observation.” “Here is an encounter.” The resource is state-oriented.

When you map HL7 to FHIR, you are translating verbs into nouns. The event (“an admission happened”) becomes a set of resources (“here is the patient, here is the encounter, here is the practitioner involved”).

This is why every HL7 message typically becomes a FHIR Bundle — a collection of resources representing the state described by the event.

Message header — MSH, EVN

MSH (Message Header)does not map to a FHIR resource directly. Instead, it informs the Bundle's metadata.

  • MSH-7 (Date/Time of Message) → Bundle.timestamp
  • MSH-9 (Message Type) → informs the choice of Bundle type and what resources to include
  • MSH-10 (Message Control ID) → Bundle.identifier
  • MSH-3 (Sending Application) → Bundle.meta.source or a custom extension

EVN (Event Type) appears in ADT messages and similarly informs the Bundle context.

  • EVN-2 (Recorded Date/Time) → Encounter.period.start for admissions
  • EVN-4 (Event Reason Code) → Encounter.reasonCode

Patient segments — PID, PD1, NK1

PID (Patient Identification) maps to the Patient resource.

HL7 v2 FieldFHIR PathNotes
PID-3 (Patient Identifier List)Patient.identifierUse system to encode the assigning authority (MRN, SSN, etc.)
PID-5 (Patient Name)Patient.nameMap family, given, prefix, suffix to corresponding HumanName fields
PID-7 (Date of Birth)Patient.birthDateFormat YYYY-MM-DD
PID-8 (Administrative Sex)Patient.genderM→male, F→female, O→other, U→unknown
PID-11 (Patient Address)Patient.addressMap to Address with use=home
PID-13 (Phone — Home)Patient.telecomsystem=phone, use=home
PID-14 (Phone — Business)Patient.telecomsystem=phone, use=work
PID-15 (Primary Language)Patient.communication.languageUse BCP-47 codes
PID-16 (Marital Status)Patient.maritalStatusMap HL7 0002 to FHIR v3-MaritalStatus
PID-22 (Ethnic Group)Patient.extension (us-core-ethnicity)US Core extension
PID-10 (Race)Patient.extension (us-core-race)US Core extension

PD1 (Patient Additional Demographics) extends Patient with additional info.

  • PD1-4 (Primary Care Provider) → Patient.generalPractitioner (reference to Practitioner)

NK1 (Next of Kin) maps to either a separate RelatedPerson resource or to Patient.contact.

  • For tight relationships (emergency contact, guardian) → Patient.contact
  • For full relationship modeling → RelatedPerson referencing the Patient

Visit segments — PV1, PV2

PV1 (Patient Visit) maps to the Encounter resource.

HL7 v2 FieldFHIR PathNotes
PV1-2 (Patient Class)Encounter.classI→inpatient, O→outpatient, E→emergency
PV1-3 (Assigned Patient Location)Encounter.location.locationReference to Location resource
PV1-7 (Attending Doctor)Encounter.participant.individualReference to Practitioner; type=ATND
PV1-8 (Referring Doctor)Encounter.participant.individualtype=REF
PV1-17 (Admitting Doctor)Encounter.participant.individualtype=ADM
PV1-19 (Visit Number)Encounter.identifier
PV1-44 (Admit Date/Time)Encounter.period.start
PV1-45 (Discharge Date/Time)Encounter.period.end
PV1-36 (Discharge Disposition)Encounter.hospitalization.dischargeDisposition

PV2 (Patient Visit Additional Info) provides additional context.

  • PV2-3 (Admit Reason) → Encounter.reasonCode
  • PV2-25 (Visit Priority Code) → Encounter.priority

For the full ADT context that drives most PV1 mapping work, see our HL7 ADT messages complete reference.

Observation segments — OBR, OBX

This is where most lab integrations live. Understanding this section well will solve 60% of your real-world mapping problems.

OBR (Observation Request) maps to ServiceRequest (and informs DiagnosticReport).

HL7 v2 FieldFHIR Path
OBR-2 (Placer Order Number)ServiceRequest.identifier (type=PLAC)
OBR-3 (Filler Order Number)ServiceRequest.identifier (type=FILL)
OBR-4 (Universal Service ID)ServiceRequest.code
OBR-7 (Observation Date/Time)DiagnosticReport.effectiveDateTime
OBR-14 (Specimen Received Date/Time)Specimen.receivedTime
OBR-16 (Ordering Provider)ServiceRequest.requester
OBR-22 (Results Rpt/Status Chng Date/Time)DiagnosticReport.issued
OBR-25 (Result Status)DiagnosticReport.status

OBX (Observation/Result) maps to Observation.

HL7 v2 FieldFHIR Path
OBX-2 (Value Type)informs how to populate Observation.value[x]
OBX-3 (Observation Identifier)Observation.code (typically LOINC)
OBX-5 (Observation Value)Observation.value[x] — type depends on OBX-2
OBX-6 (Units)Observation.valueQuantity.unit
OBX-7 (References Range)Observation.referenceRange.text
OBX-8 (Abnormal Flags)Observation.interpretation
OBX-11 (Observation Result Status)Observation.status
OBX-14 (Date/Time of Observation)Observation.effectiveDateTime
OBX-19 (Date/Time of Analysis)Observation.issued

OBX-2 value type mapping:

  • NM (numeric) → Observation.valueQuantity
  • ST (string) → Observation.valueString
  • CE/CWE (coded entry) → Observation.valueCodeableConcept
  • DT (date) → Observation.valueDateTime
  • TM (time) → Observation.valueTime

Order segments — ORC

ORC (Common Order) typically accompanies OBR and maps to additional ServiceRequest fields.

HL7 v2 FieldFHIR Path
ORC-1 (Order Control)informs ServiceRequest.status and intent
ORC-2 (Placer Order Number)ServiceRequest.identifier (type=PLAC)
ORC-3 (Filler Order Number)ServiceRequest.identifier (type=FILL)
ORC-5 (Order Status)ServiceRequest.status
ORC-9 (Date/Time of Transaction)ServiceRequest.authoredOn
ORC-12 (Ordering Provider)ServiceRequest.requester

ORC-1 (Order Control) to ServiceRequest.status mapping:

  • NW (New Order) → status=active, intent=order
  • CA (Cancel Order) → status=revoked
  • DC (Discontinue) → status=revoked
  • HD (Hold Order) → status=on-hold
  • RL (Release Previous Hold) → status=active

Allergy segments — AL1

AL1 (Patient Allergy Information) maps to AllergyIntolerance.

HL7 v2 FieldFHIR Path
AL1-2 (Allergen Type Code)AllergyIntolerance.category
AL1-3 (Allergen Code/Mnemonic/Description)AllergyIntolerance.code
AL1-4 (Allergy Severity Code)AllergyIntolerance.reaction.severity
AL1-5 (Allergy Reaction Code)AllergyIntolerance.reaction.manifestation
AL1-6 (Identification Date)AllergyIntolerance.recordedDate

Always link AllergyIntolerance back to the Patient via AllergyIntolerance.patient.

Diagnosis segments — DG1

DG1 (Diagnosis) maps to Condition.

HL7 v2 FieldFHIR Path
DG1-3 (Diagnosis Code)Condition.code (typically ICD-10)
DG1-5 (Diagnosis Date/Time)Condition.recordedDate
DG1-6 (Diagnosis Type)informs Condition.category
DG1-15 (Diagnosis Priority)extension or Condition.severity

For Encounter-related diagnoses, also populate Condition.encounter.

Procedure segments — PR1

PR1 (Procedures) maps to Procedure.

HL7 v2 FieldFHIR Path
PR1-3 (Procedure Code)Procedure.code (CPT or SNOMED)
PR1-5 (Procedure Date/Time)Procedure.performedDateTime
PR1-11 (Procedure Priority)extension
PR1-12 (Associated Diagnosis Code)Procedure.reasonCode

Scheduling segments — SCH, AIS, AIG, AIL, AIP

SIU messages carry appointment data and have a particularly complex translation. The high-level mapping:

SCH (Schedule Activity Information) maps to Appointment.

HL7 v2 FieldFHIR Path
SCH-1 (Placer Appointment ID)Appointment.identifier
SCH-3 (Filler Appointment ID)Appointment.identifier
SCH-7 (Appointment Reason)Appointment.reasonCode
SCH-11 (Appointment Timing Quantity)Appointment.start, .end, .minutesDuration
SCH-25 (Filler Status Code)Appointment.status

AIS (Appointment Information — Service) adds the type of service.

  • AIS-3 (Universal Service ID) → Appointment.serviceType

AIG (Appointment Information — General Resource), AIL (Location), and AIP (Personnel) each describe participants in the appointment. These all become Appointment.participant entries with different participant.type values.

Insurance segments — IN1, IN2, IN3

IN1 (Insurance) maps to Coverage.

HL7 v2 FieldFHIR Path
IN1-2 (Insurance Plan ID)Coverage.identifier
IN1-3 (Insurance Company ID)Coverage.payor (reference to Organization)
IN1-12 (Plan Effective Date)Coverage.period.start
IN1-13 (Plan Expiration Date)Coverage.period.end
IN1-16 (Name of Insured)Coverage.subscriber (Patient or RelatedPerson)
IN1-17 (Insured's Relationship to Patient)Coverage.relationship
IN1-36 (Policy Number)Coverage.subscriberId

IN2 and IN3 carry additional detail and are typically merged into the same Coverage resource or extensions.

Z-segments and custom data

Z-segments — segments starting with Z, like ZPD or ZIN — are the wild card of HL7 v2. They carry custom data that no standard exists for.

The FHIR equivalent is extensions. Every Z-field becomes an extension on the most relevant resource. For example, if ZPD-3 carries a custom patient attribute, it becomes:

{
  "resourceType": "Patient",
  "extension": [
    {
      "url": "https://yourorg.org/fhir/StructureDefinition/zpd-3-custom-attribute",
      "valueString": "..."
    }
  ]
}

Always define the StructureDefinitionfor any extension you create. Otherwise, you've moved the problem from “what does ZPD-3 mean” to “what does this extension mean” without solving anything.

The full message → bundle pattern

Putting it all together, here's the structure for a typical ORU^R01 message (lab result) translated to FHIR.

Source HL7 v2 message:

MSH|...|...|ORU^R01|...
PID|...
PV1|...
OBR|...
OBX|1|NM|...
OBX|2|NM|...
OBX|3|CE|...

Destination FHIR Bundle:

{
  "resourceType": "Bundle",
  "type": "message",
  "timestamp": "2026-05-12T10:23:45Z",
  "identifier": { "value": "MSH-10 value here" },
  "entry": [
    { "resource": { "resourceType": "MessageHeader", "..." : "..." } },
    { "resource": { "resourceType": "Patient", "...": "..." } },
    { "resource": { "resourceType": "Encounter", "...": "..." } },
    { "resource": { "resourceType": "ServiceRequest", "...": "..." } },
    { "resource": { "resourceType": "DiagnosticReport", "...": "..." } },
    { "resource": { "resourceType": "Observation", "...": "..." } },
    { "resource": { "resourceType": "Observation", "...": "..." } },
    { "resource": { "resourceType": "Observation", "...": "..." } }
  ]
}

Resources reference each other via reference fields — Observation.subject points to Patient/[id], Observation.encounter points to Encounter/[id], and so on.

Common mistakes and how to avoid them

Six bugs we see in nearly every HL7 → FHIR pipeline we audit:

Mistake 1 — Treating Bundle as optional

New mappers sometimes try to send a single Observation resource directly instead of wrapping it in a Bundle. This fails most FHIR servers' validation because the receiving system has no context about the patient.

Mistake 2 — Hardcoding identifier systems

PID-3 carries identifiers from multiple authorities (MRN, SSN, payer IDs). Hardcoding "system": "urn:oid:1.2.3" means every patient ends up with the wrong identifier system. Map the HD component of PID-3 (the assigning authority) to the FHIR identifier.system.

Mistake 3 — Ignoring the difference between Patient.gender and PID-8

HL7 uses M/F/O/U. FHIR uses male/female/other/unknown. Don't pass through HL7's single-letter codes — FHIR validation will reject them.

Mistake 4 — Forgetting timezone information

HL7 v2 timestamps frequently omit timezone. FHIR's dateTimefields strongly prefer timezone-qualified ISO 8601. Decide on a default timezone (typically the sending facility's local time) and apply it consistently.

Mistake 5 — Creating duplicate Patient resources

Each new HL7 message contains the patient inline. If you create a new Patient resource for every message, you end up with thousands of duplicate Patients. Use conditional create (POST /Patient?identifier=...) or maintain a local identity map.

Mistake 6 — Using the wrong status mapping

OBX-11 has values like F (Final), C (Corrected), P (Preliminary). FHIR Observation.status uses different vocabulary (final, corrected, preliminary, amended). Always map through a translation table — don't pass the values through.

Tools and validation

Useful tools when building HL7 to FHIR mappings:

  • Mirth Connect (NextGen Connect). Our weapon of choice. Built-in HL7 parser, JavaScript and Groovy transformers, and you can ship FHIR resources via the HTTP Sender. We have a full walkthrough in how to build an HL7 interface in Mirth Connect.
  • HAPI FHIR validator. Free, available as both a web tool and a CLI. Validates FHIR resources against any StructureDefinition. Use it on every Bundle you produce.
  • Inferno test suite.ONC's official suite for testing FHIR conformance, especially relevant if you need to certify against USCDI requirements.
  • LinuxForHealth FHIR Converter.Open-source tool from IBM that does pre-built HL7 to FHIR conversions. Useful as a reference implementation even if you don't use it in production — you can compare your output to its output.
  • FHIR Mapping Language (FML).A formal DSL for expressing FHIR transformations. Powerful but heavyweight — most teams don't need it for basic HL7 to FHIR work.

Next steps

If you're building a real HL7 to FHIR pipeline, the next pieces you'll need beyond this mapping reference are:

Want help building this in production?

This reference will get you 80% of the way there. The remaining 20% — the timezone edge cases, the deduplication strategy, the error handling, the throughput tuning — is where most real projects spend most of their time.

If you'd rather not figure that out from scratch, we do this work for a living. Book a 30-minute scoping call and we'll tell you what we'd build and what it would cost.

Book a scoping call →

Or run our pricing calculator to estimate what an HL7 to FHIR project would cost based on your scope.

Prefer email? info@tactionsoft.com — we reply within 4 business hours.

FAQ

Frequently Asked Questions

What does HL7 v2 PID map to in FHIR R4?
HL7 v2 PID (Patient Identification) maps to the FHIR R4 Patient resource. PID-3 becomes Patient.identifier, PID-5 becomes Patient.name, PID-7 becomes Patient.birthDate, PID-8 becomes Patient.gender (with M/F/O/U translated to male/female/other/unknown), and US Core race/ethnicity extensions hold PID-10 and PID-22. Always map the HD assigning-authority component of PID-3 to identifier.system rather than hardcoding it.
What does HL7 v2 OBX map to in FHIR R4?
HL7 v2 OBX (Observation/Result) maps to the FHIR R4 Observation resource. OBX-3 becomes Observation.code (typically LOINC), OBX-5 becomes Observation.value[x] with the type determined by OBX-2 (NM → valueQuantity, ST → valueString, CE/CWE → valueCodeableConcept), OBX-8 becomes Observation.interpretation, and OBX-11 becomes Observation.status (mapped through a translation table — never pass HL7 single-letter status codes directly).
How does an HL7 v2 ORU message become a FHIR Bundle?
A single ORU^R01 lab message becomes a FHIR Bundle containing one Patient (from PID), one Encounter (from PV1), one ServiceRequest plus one DiagnosticReport (from OBR), and one Observation per OBX segment. Each Observation references the Patient, Encounter, and DiagnosticReport by URL. The Bundle type is typically 'message' and Bundle.timestamp comes from MSH-7.
Why is HL7 to FHIR mapping harder than the spec implies?
Three tensions cause most mapping bugs: HL7 v2 is positional while FHIR is named, HL7 carries identity inline while FHIR carries identity by reference, and HL7 is a message stream while FHIR is a resource model. The official segment-to-resource tables don't capture these — you have to reason about them yourself when designing your transformer.
What happens to Z-segments when converting HL7 v2 to FHIR?
Z-segments become FHIR extensions. Each Z-field maps to an extension on the most relevant resource, with a custom StructureDefinition URL identifying it. Always define the StructureDefinition — otherwise you've moved the problem from 'what does ZPD-3 mean' to 'what does this extension mean' without solving anything.
How do I avoid creating duplicate Patient resources?
Use FHIR's conditional create pattern — POST /Patient?identifier=urn:oid:1.2.3|MRN12345 — which creates a new Patient only if one with that identifier doesn't already exist. Alternatively, maintain a local identity map keyed by the assigning-authority + MRN combination. Without one of these strategies, every HL7 message creates a new Patient and your FHIR server ends up with thousands of duplicates.
Should I use FHIR Mapping Language (FML) for HL7 to FHIR work?
Probably not for typical HL7 v2 to FHIR projects. FML is a formal DSL and is powerful, but it's heavyweight and the tooling is still maturing. Most production HL7 to FHIR pipelines are built in an integration engine like Mirth Connect using JavaScript or Groovy transformers — easier to debug, easier to hire for, and more flexible when edge cases appear.
What's the best tool for validating my generated FHIR resources?
The HAPI FHIR validator is the de-facto standard. It's free, available as a CLI and a web tool, and validates against any StructureDefinition including US Core. For conformance work tied to ONC certification, also run your Bundles through the Inferno test suite. Validate every Bundle in CI — FHIR validation failures caught at build time cost minutes; the same failures caught in production cost days.
Can Mirth Connect handle HL7 v2 to FHIR R4 conversion natively?
Yes. Mirth Connect's built-in HL7 parser exposes segments as JavaScript objects, JavaScript or Groovy transformers build the FHIR resources, and the HTTP Sender ships them as a Bundle to any FHIR server. The HL7 v2 to FHIR façade is one of the most common patterns we build for healthcare clients — see our HL7-interface tutorial for the channel structure.
Where do I get expert help with HL7 to FHIR work?
Our team builds HL7 v2 to FHIR R4 integrations for hospitals, labs, and health-tech teams across North America. Book a 30-minute scoping call to discuss your specific mapping requirements, or run our pricing calculator to see what your project would cost. Email info@tactionsoft.com — we reply within 4 business hours.

Need expert Mirth Connect support?

Whether you have a one-time integration project or need ongoing managed support, every engagement is named, scoped, and priced upfront — productized packages, no hourly billing.

Talk to a Mirth Solutions Architect

60-second form. Senior engineer responds within one business day.

What is 9 + 1 ?