FHIR MedicationRequest Resource: A Developer's Guide to Modelling Prescriptions in an Indian HMIS
If you're building a Hospital Management Information System in India, prescriptions are at the heart of your OPD workflow. Every doctor writes dozens of them a day, every pharmacy has to dispense them, and every audit trail needs to capture who prescribed what, when, and to whom. FHIR's MedicationRequest resource is the canonical way to model a prescription order in a modern, interoperable HMIS.
In this post — seventh in our FHIR Resource Deep-Dives series for Indian healthcare IT — we'll walk through MedicationRequest end to end. We'll see how it differs from the closely-related MedicationStatement and MedicationDispense resources, dig into the fields you'll actually use in production, and build a working Spring Boot service that creates and reads prescriptions. Along the way, we'll wire it into the India-specific concerns that come up in real HMIS work: Jan Aushadhi generic substitution, DPCO ceiling price enforcement, NLEM-aligned essential medicines, and ABHA-linked prescription records.
MedicationRequest vs MedicationStatement vs MedicationDispense
FHIR gives you three resources for tracking medications, and they map to three different moments in the medication lifecycle:
-
MedicationRequest— an order or prescription. A doctor says "give this patient amoxicillin 500mg three times a day for 7 days." The order is created, but the patient may or may not have started taking it. -
MedicationStatement— a record of what the patient is actually taking. "Patient reports taking metformin 500mg twice daily for the past 6 months." Useful for medication reconciliation at admission. -
MedicationDispense— a record of what the pharmacy actually handed out. The medication may have been dispensed against aMedicationRequest(soMedicationDispensewill reference the prescription viaauthorizingPrescription), or it may have been an OTC sale with no prescription involved.
In your HMIS, the typical OPD-to-pharmacy flow is:
- OPD consultation ends → doctor creates a
MedicationRequest(the prescription). - Patient walks to pharmacy → pharmacy creates a
MedicationDispensereferencing theMedicationRequest. - Patient is admitted to IPD next month → on admission, you create a
MedicationStatementsummarising their home medications for reconciliation.
The three resources are linked by MedicationRequest.basedOn (the plan or order this request is based on), MedicationRequest.replaces (a previous prescription this one supersedes), and MedicationDispense.authorizingPrescription (the MedicationRequest this dispense fulfils). Get the linkage right and the audit trail writes itself; get it wrong and you spend six months untangling dispensations.
Anatomy of a MedicationRequest
The R4 spec defines MedicationRequest with these key fields you'll use in production:
| Field | Cardinality | Purpose |
|---|---|---|
status |
1..1 |
active / completed / stopped / draft / unknown / cancelled / entered-in-error
|
intent |
1..1 |
proposal / plan / order / original-order / reflex-order / filler-order
|
medicationCodeableConcept |
1..1 | The drug prescribed (RxNorm, SNOMED CT, ATC, or local coding system) |
subject |
1..1 | Reference to the Patient (ABHA-linked in India) |
encounter |
0..1 | Reference to the Encounter (the OPD visit) |
authoredOn |
0..1 | When the prescription was written |
requester |
0..1 | Reference to the Practitioner who wrote it (HPR ID in India) |
dosageInstruction |
0..* | How the patient should take it (dose, route, frequency, duration) |
dispenseRequest |
0..1 | Pharmacy-side info: quantity, refills, validity period, expected supply duration |
substitution |
0..1 | Whether generic substitution is allowed at the pharmacy |
note |
0..* | Free-text notes ("take after meals", "avoid driving") |
priorPrescription |
0..1 | Reference to a previous MedicationRequest this one replaces |
detectedIssue |
0..* | Links to allergy / interaction warnings raised at prescribing time |
A minimal but realistic MedicationRequest for an Indian OPD looks like this:
{
"resourceType": "MedicationRequest",
"id": "prescription-2026-06-23-001",
"status": "active",
"intent": "order",
"medicationCodeableConcept": {
"coding": [
{
"system": "http://www.nlm.nih.gov/research/umls/rxnorm",
"code": "197361",
"display": "Amoxicillin 500mg"
},
{
"system": "https://janaushadhi.gov.in/product-codes",
"code": "JAP-AMX-500",
"display": "Jan Aushadhi Amoxicillin 500mg (generic)"
}
],
"text": "Amoxicillin 500mg (generic substitution allowed)"
},
"subject": {
"reference": "Patient/abha-14-1234-5678-9012",
"display": "Ravi Kumar"
},
"encounter": {
"reference": "Encounter/opd-2026-06-23-1045"
},
"authoredOn": "2026-06-23T10:45:00+05:30",
"requester": {
"reference": "Practitioner/hpr-MCI-12345",
"display": "Dr. Priya Shah"
},
"dosageInstruction": [
{
"text": "One tablet three times a day for 7 days, after meals",
"timing": {
"repeat": {
"frequency": 3,
"period": 1,
"periodUnit": "d"
}
},
"doseAndRate": [
{
"doseQuantity": {
"value": 500,
"unit": "mg",
"system": "http://unitsofmeasure.org",
"code": "mg"
}
}
],
"route": {
"coding": [
{
"system": "http://snomed.info/sct",
"code": "26643006",
"display": "Oral route"
}
]
}
}
],
"dispenseRequest": {
"quantity": {
"value": 21,
"unit": "tablets"
},
"numberOfRepeatsAllowed": 0,
"validityPeriod": {
"start": "2026-06-23",
"end": "2026-07-23"
},
"expectedSupplyDuration": {
"value": 7,
"unit": "days",
"system": "http://unitsofmeasure.org",
"code": "d"
}
},
"substitution": {
"allowedBoolean": true,
"reason": {
"text": "Generic substitution permitted per Jan Aushadhi scheme guidelines"
}
}
}
Drug coding in the Indian context
Drug coding is where most Indian HMIS implementations fall down. The international standards — RxNorm (US-centric), SNOMED CT (clinical, not drug-focused), ATC (WHO classification) — all work, but they don't capture the specific regulatory context that matters in India:
-
Jan Aushadhi (janaushadhi.gov.in): The government's affordable generic-medicine scheme run under the Pharmaceuticals & Medical Devices Bureau of India (PMBI). Every Jan Aushadhi product has a unique product code, and the prescription should allow the pharmacist to substitute a Jan Aushadhi generic for a branded equivalent. The
substitution.allowedBooleanfield is critical here — set it totrueand the pharmacy can offer the patient a Jan Aushadhi alternative, typically at 50–90% lower cost. The Indian government has been pushing for broader Jan Aushadhi adoption under the Jan Aushadhi Suvidha and Jan Samarthan campaigns, so any new HMIS should support it out of the box. -
DPCO (Drug Prices Control Order): NPPA (National Pharmaceutical Pricing Authority) sets ceiling prices for scheduled drugs under the DPCO, 2013. Your HMIS should validate that the dispensed price doesn't exceed the DPCO ceiling. You can extend
MedicationRequestwith a custom extension likedispenseRequest.extension.dpco-ceiling-priceto record the regulatory cap, and your pharmacy module can hard-stop a dispense that exceeds it. - NLEM (National List of Essential Medicines): A subset of drugs that are considered essential for India's healthcare needs, published by the Ministry of Health. Useful for public-hospital implementations where the formulary is restricted to NLEM drugs. Most government schemes (CGHS, ESIC, Ayushman Bharat) reimburse only NLEM drugs.
-
Brand vs generic: Indian prescriptions often specify a brand name (e.g. "Mox 500" for a branded amoxicillin by Ranbaxy). Your mapping layer should resolve brand → generic → RxNorm/SNOMED → Jan Aushadhi code, so the prescription is both human-readable and machine-interoperable. The doctor types the brand, the patient sees a friendly label, but the
MedicationRequestcarries the full set of codings.
A common pattern in ArogyaPlus is to keep an internal drug master table (with columns for brand name, generic name, strength, manufacturer, RxNorm code, SNOMED code, Jan Aushadhi code, DPCO ceiling price, NLEM flag, schedule H/H1/X classification), and resolve all of these at the point of prescription creation.
Spring Boot + HAPI FHIR code
Here's a working Spring Boot service that creates a MedicationRequest from an OPD prescription form, persists it, and exposes it via REST:
@Service
public class PrescriptionService {
private final FhirContext fhirContext = FhirContext.forR4();
private final IParser parser = fhirContext.newJsonParser();
@Autowired
private MedicationRequestDao medicationRequestDao;
@Autowired
private DrugMasterRepository drugMasterRepository;
public MedicationRequest createPrescription(PrescriptionRequest request) {
// Look up the drug in our internal drug master
DrugMaster drug = drugMasterRepository.findById(request.getDrugId())
.orElseThrow(() -> new DrugNotFoundException(request.getDrugId()));
// Build the MedicationRequest
MedicationRequest medReq = new MedicationRequest();
medReq.setId("prescription-" + UUID.randomUUID());
medReq.setStatus(MedicationRequest.MedicationRequestStatus.ACTIVE);
medReq.setIntent(MedicationRequest.MedicationRequestIntent.ORDER);
// Drug coding — RxNorm + Jan Aushadhi + brand
CodeableConcept medication = new CodeableConcept();
medication.addCoding()
.setSystem("http://www.nlm.nih.gov/research/umls/rxnorm")
.setCode(drug.getRxnormCode())
.setDisplay(drug.getGenericName() + " " + drug.getStrength());
if (drug.getJanAushadhiCode() != null) {
medication.addCoding()
.setSystem("https://janaushadhi.gov.in/product-codes")
.setCode(drug.getJanAushadhiCode())
.setDisplay("Jan Aushadhi " + drug.getGenericName());
}
medication.setText(drug.getBrandName() + " (" + drug.getGenericName() + " " + drug.getStrength() + ")");
medReq.setMedication(medication);
// Patient reference (ABHA)
medReq.setSubject(new Reference("Patient/" + request.getPatientAbhaId()));
// Encounter reference
medReq.setEncounter(new Reference("Encounter/" + request.getEncounterId()));
// Authored timestamp
medReq.setAuthoredOn(new Date());
// Prescribing doctor
medReq.setRequester(new Reference("Practitioner/" + request.getPractitionerHprId())
.setDisplay(request.getDoctorName()));
// Dosage instructions
Dosage dosage = new Dosage();
dosage.setText(request.getDosageText());
Timing timing = new Timing();
timing.getRepeat()
.setFrequency(request.getFrequencyPerDay())
.setPeriod(1)
.setPeriodUnit(Timing.UnitsOfTime.D);
dosage.setTiming(timing);
Dosage.DosageDoseAndRateComponent doseAndRate = new Dosage.DosageDoseAndRateComponent();
doseAndRate.getDoseQuantity()
.setValue(drug.getStrengthValue())
.setUnit(drug.getStrengthUnit());
dosage.addDoseAndRate(doseAndRate);
medReq.addDosageInstruction(dosage);
// Dispense request — quantity, refills, DPCO validation
MedicationRequest.MedicationRequestDispenseRequestComponent dispense =
new MedicationRequest.MedicationRequestDispenseRequestComponent();
dispense.setQuantity(new Quantity()
.setValue(request.getTotalQuantity())
.setUnit("tablets"));
dispense.setNumberOfRepeatsAllowed(request.getRefills());
if (request.getRefills() > 0) {
dispense.setValidityPeriod(new Period()
.setStart(new Date())
.setEnd(DateUtils.addMonths(new Date(), 6)));
}
// DPCO ceiling price extension
if (drug.getDpcoCeilingPrice() != null) {
dispense.addExtension()
.setUrl("https://nppa.gov.in/fhir/StructureDefinition/dpco-ceiling-price")
.setValue(new Money()
.setValue(drug.getDpcoCeilingPrice())
.setCurrency("INR"));
}
medReq.setDispenseRequest(dispense);
// Substitution policy
MedicationRequest.MedicationRequestSubstitutionComponent substitution =
new MedicationRequest.MedicationRequestSubstitutionComponent();
substitution.setAllowedBoolean(Boolean.TRUE);
substitution.setReason(new CodeableConcept().setText(
"Generic substitution permitted per Jan Aushadhi scheme guidelines"));
medReq.setSubstitution(substitution);
// Persist
return medicationRequestDao.save(medReq);
}
public String toJson(MedicationRequest medReq) {
return parser.encodeResourceToString(medReq);
}
}
And the REST controller:
@RestController
@RequestMapping("/api/fhir/MedicationRequest")
public class MedicationRequestController {
@Autowired
private PrescriptionService prescriptionService;
@Autowired
private MedicationRequestDao medicationRequestDao;
@PostMapping
public ResponseEntity<String> create(@RequestBody PrescriptionRequest request) {
MedicationRequest created = prescriptionService.createPrescription(request);
return ResponseEntity
.status(HttpStatus.CREATED)
.header("Content-Location", "/api/fhir/MedicationRequest/" + created.getId())
.body(prescriptionService.toJson(created));
}
@GetMapping("/{id}")
public ResponseEntity<String> get(@PathVariable String id) {
return medicationRequestDao.findById(id)
.map(prescriptionService::toJson)
.map(json -> ResponseEntity.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(json))
.orElse(ResponseEntity.notFound().build());
}
}
The MedicationRequestDao is your standard JPA / Mongo / HAPI FHIR JPA repository — pick whatever persistence layer matches the rest of your HMIS. The HAPI FHIR structure handles all of the FHIR-specific validation, codings, and extensions for you.
ABDM integration: prescriptions as part of a Bundle
In the ABDM (Ayushman Bharat Digital Mission) ecosystem, a prescription is rarely a standalone resource. It's part of a Bundle — typically a document or transaction bundle — that ties the prescription to the patient, the encounter, the prescribing doctor, and any observations (vitals, lab results) that informed the prescription.
A common pattern is to build the prescription bundle at the end of the OPD visit, sign it digitally (using ABDM's digital signature specifications), and push it to the patient's PHR (Personal Health Record) address via the /v0.5/health-information/transfer flow. The bundle typically contains:
-
Patient(with ABHA ID) -
Encounter(the OPD visit) -
Practitioner(the doctor, with HPR ID) -
Organization(the hospital, with HFR ID) - One or more
MedicationRequestresources - Related
Observationresources (vitals taken during the visit) - A
Compositionresource as the bundle's first entry (the document manifest, pointing at all the above)
This is how ABDM achieves longitudinal health records — every prescription you've ever written is linked to the patient's ABHA and can be pulled up by any other hospital in the network (with the patient's consent, of course). And because everything is a MedicationRequest with proper RxNorm / SNOMED / Jan Aushadhi codings, the receiving hospital can immediately understand, verify, and continue the prescription course.
Common pitfalls
A few things to watch out for in production:
- Drug master consistency: If your drug master is out of sync with the latest Jan Aushadhi or DPCO lists, your prescription will have stale codings. Automate the sync — Jan Aushadhi publishes its product list as a downloadable CSV, and NPPA publishes DPCO ceiling prices quarterly. Schedule a nightly job to pull and reconcile.
-
Substitution consent: Don't enable generic substitution by default for every drug. Some drugs (e.g. narrow-therapeutic-index drugs like warfarin, levothyroxine, theophylline) should not be substituted without explicit doctor approval. The
substitution.allowedBooleanfield is per-prescription, not per-patient — use it carefully. Better yet, drive it from the drug master: NLEM drugs and Jan Aushadhi equivalents default to allowed; narrow-therapeutic-index drugs default to disallowed. -
Refill vs renewal:
numberOfRepeatsAllowedmeans "the pharmacy can dispense this prescription N more times without a new doctor visit." A "renewal" (a new prescription for the same drug after the original course ends) is a newMedicationRequestthatreplacesthe old one viapriorPrescription. Don't conflate the two in your pharmacy UI. -
Status transitions: A prescription can go from
active→completed(course finished) oractive→stopped(doctor cancelled it). If you setstatus = stopped, also recordstatusReason— this is critical for clinical audit and for explaining to the patient why their medication was stopped. -
Time zone: India is a single time zone (IST, UTC+5:30) now, but your FHIR resources should still carry explicit
+05:30offsets in all timestamps. Don't strip the offset; you will regret it the first time you integrate with a system in another country. -
Don't store drug name in
medicationCodeableConcept.textonly: Thetextfield is for human display. The codings in thecodingarray are for machine interoperability. If you only filltextand leavecodingempty, you've built a fax machine, not an HMIS.
Closing thought
MedicationRequest is the resource that ties together the clinical (what was prescribed), regulatory (DPCO, Jan Aushadhi), and interoperability (ABDM) sides of Indian healthcare IT. Get the model right and the rest of your prescription workflow — pharmacy dispensing, drug interaction checks, formulary enforcement, ABHA-linked longitudinal records — becomes a matter of building the next resource on top.
If you're building an HMIS, start with Patient, Practitioner, Encounter, and MedicationRequest. That's the core outpatient loop. Everything else — Observation, Condition, Procedure, Immunization, AllergyIntolerance — extends from it. In the next post, we'll look at DiagnosticReport — the FHIR resource for NABL-accredited lab reports and radiology results — and how to tie it back into the same OPD visit.
Harikrushna V is the founder of Orglance Technologies — an India-based software services company specialising in Java/Spring Boot, healthcare IT, and fintech systems — and SnowCare Health Tech (ArogyaPlus HMIS). 13 years building enterprise systems across PayPal, Salesforce, and NCR. Building dedicated India engineering teams for US/UK/EU/Japan clients. Reach out: hello@orglancetechnologies.com

