Medication Data Exchange
Medication Data Exchange
Medication data is simultaneously one of the most exchanged and most misimplemented data types in FHIR. The pattern failure is almost always the same: developers pick the medication resource that sounds right and ignore the others, or they treat all four medication resources as interchangeable containers for “drugs a patient is taking.”
They are not interchangeable. FHIR models the medication lifecycle as four distinct resources, each representing a different clinical event with different implied intent. Using the wrong resource causes downstream systems to misinterpret clinical intent — a prescription treated as a medication history, or a patient-reported drug treated as an active order. These are not cosmetic errors.
The four-resource lifecycle
| Resource | Clinical Event | Who Creates It | Drives |
|---|---|---|---|
MedicationRequest | A clinician writes a prescription or places a medication order | Prescribing provider / EHR | Dispensing at the pharmacy |
MedicationDispense | A pharmacy fills or partially fills a prescription | Pharmacy system | Medication Administration Record (MAR) |
MedicationAdministration | A nurse or automated system administers a dose | Nursing / MAR system | Medication reconciliation; billing |
MedicationStatement | A medication is reported — patient says they take it, or external record says so | Patient / referring provider / care coordinator | Medication reconciliation; problem list context |
The chain of custody: a MedicationRequest drives a MedicationDispense (linked via basedOn), which informs a MedicationAdministration. MedicationStatement stands apart — it is not part of the prescribe-dispense-administer chain because it does not imply that a prescription was written or that the medication was dispensed from a known pharmacy.
MedicationRequest
Use MedicationRequest when a clinician is ordering or prescribing a medication. This resource represents intent: “this patient should receive this medication.”
The intent field is required and determines the nature of the request:
| Intent value | Meaning |
|---|---|
proposal | A suggestion — from a care plan or decision support; not yet ordered |
plan | Part of a care plan; expected to be ordered at an appropriate time |
order | A prescription or medication order — the standard clinical scenario |
original-order | The original order (when an order has been subsequently revised) |
reflex-order | Automatically generated in response to another order or result |
filler-order | The pharmacy’s interpretation of the order (less common in FHIR contexts) |
instance-order | A specific administration instruction derived from the order |
option | A choice among alternatives |
For the standard “clinician prescribes medication” scenario, intent is order. For a care plan recommendation that has not yet been ordered, use proposal. Getting intent wrong causes receiving systems to misclassify the request — a proposal should not trigger pharmacy dispensing.
MedicationDispense
Use MedicationDispense when a pharmacy fills a prescription. This resource records what was actually dispensed — which may differ from what was prescribed (different quantity, substitution, partial fill).
basedOn links back to the MedicationRequest. If the dispense is not linked to a request — because the prescription came in on paper or via fax and was not captured as a FHIR resource — basedOn may be absent, but document this in your interface specification.
whenPrepared and whenHandedOver are distinct: preparation time vs the time the patient received the medication. Both matter for medication adherence tracking.
MedicationAdministration
Use MedicationAdministration when a dose was given in a clinical setting. This is the inpatient/infusion-center resource. It records that a specific dose was administered to a patient at a specific time by a specific practitioner.
Do not use MedicationAdministration for outpatient self-administration — there is no clinical record of that event. When a patient takes their home medication, you have a MedicationStatement, not a MedicationAdministration.
effective[x] records when administration occurred. Use effectiveDateTime for a point-in-time administration and effectivePeriod for an infusion or administration over a duration.
MedicationStatement
Use MedicationStatement for any reported medication: the patient says they take it, a referring provider documented it, an external record includes it, or a care coordinator collected it on admission. MedicationStatement does not imply that a prescription exists in your system or that the medication was dispensed from a known pharmacy.
The most common MedicationStatement error is using it for prescriptions. If a clinician is writing a new prescription, that is a MedicationRequest. MedicationStatement is for the medication history — what the patient was taking before they arrived, or what an external system reported.
informationSource identifies who reported the medication. This is clinically meaningful: a patient-reported medication history carries different reliability than a pharmacy claims feed.
JSON examples
MedicationRequest with structured dosage
{
"resourceType": "MedicationRequest",
"id": "rx-metformin-001",
"status": "active",
"intent": "order",
"medicationCodeableConcept": {
"coding": [
{
"system": "http://www.nlm.nih.gov/research/umls/rxnorm",
"code": "861007",
"display": "Metformin Hydrochloride 500 MG Oral Tablet"
}
]
},
"subject": {
"reference": "Patient/pat-001",
"display": "John Smith"
},
"encounter": {
"reference": "Encounter/enc-20231015"
},
"authoredOn": "2023-10-15",
"requester": {
"reference": "Practitioner/pract-jones",
"display": "Dr. Sarah Jones"
},
"dosageInstruction": [
{
"text": "Take 1 tablet by mouth twice daily with meals",
"timing": {
"repeat": {
"frequency": 2,
"period": 1,
"periodUnit": "d",
"when": ["MORN", "EVE"]
}
},
"route": {
"coding": [
{
"system": "http://snomed.info/sct",
"code": "26643006",
"display": "Oral route"
}
]
},
"doseAndRate": [
{
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/dose-rate-type",
"code": "ordered"
}
]
},
"doseQuantity": {
"value": 1,
"unit": "tablet",
"system": "http://terminology.hl7.org/CodeSystem/v3-orderableDrugForm",
"code": "TAB"
}
}
]
}
],
"dispenseRequest": {
"numberOfRepeatsAllowed": 5,
"quantity": {
"value": 60,
"unit": "tablet"
},
"expectedSupplyDuration": {
"value": 30,
"unit": "days",
"system": "http://unitsofmeasure.org",
"code": "d"
}
},
"substitution": {
"allowedBoolean": true
}
}
MedicationStatement for reported home medication
{
"resourceType": "MedicationStatement",
"id": "medstmt-lisinopril-001",
"status": "active",
"medicationCodeableConcept": {
"coding": [
{
"system": "http://www.nlm.nih.gov/research/umls/rxnorm",
"code": "314076",
"display": "Lisinopril 10 MG Oral Tablet"
}
]
},
"subject": {
"reference": "Patient/pat-001"
},
"context": {
"reference": "Encounter/enc-20231015"
},
"effectivePeriod": {
"start": "2021-06-01"
},
"dateAsserted": "2023-10-15",
"informationSource": {
"reference": "Patient/pat-001",
"display": "John Smith (self-reported)"
},
"dosage": [
{
"text": "10mg once daily",
"timing": {
"repeat": {
"frequency": 1,
"period": 1,
"periodUnit": "d"
}
},
"doseAndRate": [
{
"doseQuantity": {
"value": 10,
"unit": "mg",
"system": "http://unitsofmeasure.org",
"code": "mg"
}
}
]
}
]
}
Coding systems by jurisdiction
| Jurisdiction | Preferred System | Code Level for Prescribing | Code Level for Dispensing | System URI |
|---|---|---|---|---|
| United States | RxNorm | SCD or SBD | NDC | http://www.nlm.nih.gov/research/umls/rxnorm |
| United Kingdom | dm+d | VMP (generic) or AMP (brand) | AMP | https://dmd.nhs.uk |
| Australia | AMT | MPP (medicinal product pack) | MPUU or TPU | http://snomed.info/sct (AMT is a SNOMED extension) |
| New Zealand | NZMT | MP (medicinal product) | MPUU or TPU | http://nzmt.org.nz |
For US implementations, use SCD (Semantic Clinical Drug) codes for MedicationRequest — they encode ingredient, strength, and dose form, which is exactly what a prescription needs. NDC codes are appropriate in MedicationDispense because they identify the specific packaged product that was dispensed. Using NDC in MedicationRequest is incorrect: NDC codes change when manufacturers reformulate products or change package sizes, making historical medication records unreliable.
The medication[x] decision
FHIR medication resources accept the medication as either an inline medicationCodeableConcept or a reference to a separate Medication resource (medicationReference). This is the medication[x] pattern.
Use medicationCodeableConcept when the medication is fully and unambiguously described by a standard code (RxNorm SCD or SBD). This covers the large majority of commercial medications. It is simpler, requires fewer resources, and is what US Core expects.
Use medicationReference (with a separate Medication resource) when:
- The medication is a custom compound not representable by a standard code
- The formulation requires detailed ingredient information
- Multiple resources reference the same non-standard medication and you want to avoid duplication
The US Core MedicationRequest profile allows both options. Receiving systems must handle both. Do not assume medicationCodeableConcept will always be present — a server that returns medicationReference is compliant.
US Core medication profiles
US Core v6 and later defines profiles for MedicationRequest (required for US FHIR APIs). Key constraints:
statusis required; must be a value from the MedicationRequest status value setintentis required; must be one of the defined intent codesmedication[x]is required; RxNorm coding is expected for US implementationssubjectis required; must reference a Patientrequesteris requiredauthoredOnis required
Failing to populate any required element causes US Core validation to fail. Servers must also support search by patient, status, and intent.
Dosage instruction complexity
The dosageInstruction element is where most implementations cut corners. The most common shortcut: providing only dosageInstruction.text without structured timing.
A receiving system cannot act on "Take twice daily" as a string. It cannot:
- Verify administration timing against the MAR
- Trigger PRN alerts when a dose is due
- Check against maximum dose-per-period rules
- Interface with automated dispensing cabinet software
Structured dosage is not optional if downstream systems need to act on it. Populate both text (required by US Core for human readability) and the structured elements.
Key dosage elements
| Element | Type | Example |
|---|---|---|
timing.repeat.frequency | Integer | 2 (twice) |
timing.repeat.period | Decimal | 1 (per…) |
timing.repeat.periodUnit | Code (UCUM) | d (day) |
timing.repeat.when | Code list | ["MORN", "EVE"] |
timing.repeat.offset | Minutes | 30 (30 minutes before/after event) |
doseAndRate.doseQuantity | Quantity | {value: 500, unit: "mg"} |
doseAndRate.doseRange | Range | {low: 250mg, high: 500mg} — for variable dosing |
route | CodeableConcept | SNOMED oral route code |
asNeededBoolean | Boolean | true for PRN |
maxDosePerPeriod | Ratio | {numerator: 2000mg, denominator: 1 day} |
If the source system only provides free text and structured dosage is needed downstream, a human review or NLP step is required. Document this gap in the interface specification and do not silently truncate dosage data.
Substitution
MedicationRequest.substitution tells the pharmacy whether generic substitution is permitted. Omitting this element when dispensing information matters is an interface error — the pharmacy may either default to allowing substitution or reject the request for missing required information.
substitution.allowedBoolean:true= generic substitution allowed;false= dispense as writtensubstitution.allowedCodeableConcept: for more granular control (e.g., allowed for therapeutic class but not formulation)
Always populate substitution in MedicationRequest resources that will be sent to a pharmacy system.
Medication reconciliation patterns
Medication reconciliation on hospital admission involves collecting medication history from multiple sources, resolving conflicts, and producing a verified current medication list. The FHIR pattern:
- Collect MedicationStatements from multiple sources:
- Patient self-report (informationSource = Patient)
- Pharmacy claims feed (informationSource = Organisation — pharmacy or PBM)
- Referring provider summary (informationSource = Practitioner or Organisation)
- Previous encounter records (informationSource = Encounter)
- Present conflicting records to the clinician for review
- Clinician reconciles: confirms, modifies, or discontinues each medication
- Confirmed, active medications become MedicationRequests (with
intent=orderorplan) - Discontinued medications: set
status=stoppedon the MedicationRequest or MedicationStatement
The reconciliation event itself can be documented as a clinical note or as a Composition resource referencing the reconciled medication list.
Cross-system medication exchange
When sending a MedicationRequest to a pharmacy system, the Patient reference must be resolvable by the receiving system. This is the same identifier problem described in patient matching: if your Patient resource carries an MRN as its only identifier, and the pharmacy does not know your MRN namespace, the pharmacy cannot look up the patient.
For cross-organisation medication exchange:
- Include multiple Patient identifiers (MRN with system URI, insurance ID, and optionally SSN last 4)
- Confirm with the receiving system which identifier they use for patient lookup
- If using a medication order network (Surescripts/NewRx for e-prescribing), follow that network’s patient identifier requirements — which are separate from FHIR conventions
Common mistakes
Using MedicationRequest for reported medication history. If the clinician is documenting what the patient was taking at home, use MedicationStatement. MedicationRequest implies an order was placed; MedicationStatement implies it was reported.
Using MedicationStatement for a new prescription. If the clinician is writing a new prescription, that is a MedicationRequest with intent = order. MedicationStatement does not drive dispensing.
Omitting intent on MedicationRequest. It is a required field. A MedicationRequest without intent fails US Core validation and may be interpreted incorrectly by receiving systems.
Omitting status. Every medication resource has a required status. A resource without status is invalid and unusable by downstream systems for filtering active vs discontinued medications.
Providing only dosageInstruction.text. String dosage is human-readable; it is not machine-actionable. If downstream systems need to act on dosage timing, populate the structured elements.
Using NDC codes in MedicationRequest. NDC is a dispensing identifier, not a prescribing identifier. Use RxNorm SCD or SBD for prescriptions.
Missing substitution when sending to pharmacy. The pharmacy needs substitution instructions. Omitting it produces ambiguity that different pharmacy systems resolve differently — not safely.
Not populating authoredOn. Receiving systems use this to determine currency of the order. Without it, medication reconciliation systems cannot determine whether a prescription is recent or years old.